Обсуждение: Proposed refactoring of planner header files

Поиск
Список
Период
Сортировка

Proposed refactoring of planner header files

От
Tom Lane
Дата:
In <6044.1548524131@sss.pgh.pa.us> I worried about how much planner
stuff that patch might end up dragging into files that contain
planner support functions, and suggested that we could refactor the
planner's header files to reduce the inclusion footprint.  Attached
are some proposed patches to improve the situation.  The work isn't
fully done yet, but I was hoping to get buy-in on this approach
before going further.

The basic idea here is to create a new header file, which I chose
to call optimizer/optimizer.h, and put into it just the stuff that
"outside" callers of the planner might need.  For this purpose
"outside" can be approximated as "doesn't really need to know what
is in relation.h", ie Paths and related data structures.  I expect
that planner support functions will mostly be working with parsetree
data structures for their functions, so they should fit that
restriction.  In some cases they need to be able to pass a PlannerInfo
pointer through to some planner function they want to call, but they
can treat the pointer as an opaque type.  This worked out pretty well,
as I was able to eliminate uses of all other optimizer/ headers (and,
therefore, relation.h) from all but four or five files outside
backend/optimizer/.  The holdouts are mostly places that are pretty
much in bed with the planner anyway, such as statistics/dependencies.c.

I did not attempt to narrow the API used by FDWs, so file_fdw and
postgres_fdw are two of the main places that still need other
optimizer/ headers.  It might be useful to do a similar exercise
focusing on the API seen by FDWs, but that's for another time.

Also, I didn't work on tightening selfuncs.c's dependencies.
While I don't have a big problem with considering selfuncs.c to be
in bed with the planner, that's risky in that whatever dependencies
selfuncs.c has may well apply to extensions' selectivity estimators too.
What I'm thinking about doing there is trying to split selfuncs.c into
two parts, one being infrastructure that can be tightly tied to the
core planner (and, likely, get moved under backend/optimizer/) and the
other being estimators that use a limited API and can serve as models
for extension code.  But I haven't tried to do that yet, and would like
to get the attached committed first.

There are three patches attached:

0001 takes some very trivial support functions out of clauses.c and
puts them into the generic node support headers (nodeFuncs.h and
makefuncs.h) where they arguably should have been all along.  I also
took the opportunity to rename and_clause() and friends into
is_andclause() etc, to make it clearer that they are node-type-testing
functions not node-construction functions, and improved the style a
bit by using "static inline" where practical.

0002 adjusts the API of flatten_join_alias_vars() and some subsidiary
functions so that they take a Query not a PlannerInfo to define the
context they're using for Var transformation.  This makes it greatly
less ugly for parse_agg.c to call that function.  Without this change
it's impossible for parse_agg.c to be decoupled from relation.h.
It likely also saves some tiny number of cycles, by removing one level
of pointer indirection within that processing.

0003 then creates optimizer.h, moves relevant declarations there, and
adjusts #includes as needed.

Since I was intentionally trying to limit what optimizer.h pulls in,
and in particular not let it include relation.h, I needed an opaque
typedef for PlannerInfo.  On the other hand relation.h also needs to
typedef that.  I fixed that with a method that we've not used in our
code AFAIK, but is really common in system headers: there's a #define
symbol to remember whether we've created the typedef, and including
both headers in either order will work fine.

optimizer.h exposes a few of the planner's GUCs, but just the basic
cost parameters, which are likely to be useful to planner support
functions.  Another choice is to expose all of them, but I'm not
sure that's a great idea --- see gripe below for an example of why
that can encourage broken coding.

I intentionally limited 0003 to just do header refactoring, not code
motion, so there are some other follow-on tasks I'm thinking about.
Notably:

I'm really unhappy that force_parallel_mode and
parallel_leader_participation are being treated as planner GUCs.
They are not that, IMO, because they also affect the behavior of
the executor, cf HandleParallelMessage, ExecGather, ExecGatherMerge.
This is somewhere between ill-considered and outright broken: what
happens if the values change between planning and execution?  I think
we probably need to fix things so that those variables do not need to
be looked at by the executor, carrying them forward in the plan
tree if necessary.  Then they'd not need to be exposed by
optimizer.h, and indeed I think the mentioned modules wouldn't
need any optimizer inclusions anymore.

Most everything that's being exposed from tlist.c and var.c could be
argued to be generic parsetree-manipulation support that should be
somewhere else, perhaps backend/nodes/ or backend/parser/.  If we
moved those functions, I think we could get to a place where
backend/parser/ doesn't use any optimizer headers at all, which seems
like a good idea from a modularity standpoint.

Likewise, maybe expand_function_arguments() should be elsewhere.

It seems like evaluate_expr() probably doesn't belong in the planner
at all; it looks like an executor function doesn't it?

It seems possible that cost_qual_eval() should be exposed by
optimizer.h, but to do so we'd need to expose struct QualCost,
which is a creature of relation.h ATM.  Seeing that typedef Cost
is already in nodes.h, maybe it wouldn't be too awful to put
QualCost there too?

I would have exposed estimate_rel_size, which is needed by 
access/hash/hash.c, except that it requires Relation and
BlockNumber typedefs.  The incremental value from keeping
hash.c from using plancat.h probably isn't worth widening
optimizer.h's #include footprint further.  Also, I wonder
whether that whole area needs a rethink for pluggable storage.

Comments?

            regards, tom lane

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index ae7f038..5d859b7 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -22,6 +22,7 @@
 #include "foreign/fdwapi.h"
 #include "jit/jit.h"
 #include "nodes/extensible.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planmain.h"
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index 574e7bc..1526960 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -33,8 +33,8 @@
 #include "executor/executor.h"
 #include "executor/nodeSubplan.h"
 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "miscadmin.h"
-#include "optimizer/clauses.h"
 #include "utils/array.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -888,7 +888,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
             /* single combining operator */
             oplist = list_make1(subplan->testexpr);
         }
-        else if (and_clause((Node *) subplan->testexpr))
+        else if (is_andclause(subplan->testexpr))
         {
             /* multiple combining operators */
             oplist = castNode(BoolExpr, subplan->testexpr)->args;
diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c
index b7a8725..8daf09c 100644
--- a/src/backend/executor/nodeTidscan.c
+++ b/src/backend/executor/nodeTidscan.c
@@ -28,7 +28,7 @@
 #include "executor/execdebug.h"
 #include "executor/nodeTidscan.h"
 #include "miscadmin.h"
-#include "optimizer/clauses.h"
+#include "nodes/nodeFuncs.h"
 #include "storage/bufmgr.h"
 #include "utils/array.h"
 #include "utils/rel.h"
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 079d016..7085ed2 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -599,6 +599,141 @@ makeFuncCall(List *name, List *args, int location)
 }

 /*
+ * make_opclause
+ *      Creates an operator clause given its operator info, left operand
+ *      and right operand (pass NULL to create single-operand clause),
+ *      and collation info.
+ */
+Expr *
+make_opclause(Oid opno, Oid opresulttype, bool opretset,
+              Expr *leftop, Expr *rightop,
+              Oid opcollid, Oid inputcollid)
+{
+    OpExpr       *expr = makeNode(OpExpr);
+
+    expr->opno = opno;
+    expr->opfuncid = InvalidOid;
+    expr->opresulttype = opresulttype;
+    expr->opretset = opretset;
+    expr->opcollid = opcollid;
+    expr->inputcollid = inputcollid;
+    if (rightop)
+        expr->args = list_make2(leftop, rightop);
+    else
+        expr->args = list_make1(leftop);
+    expr->location = -1;
+    return (Expr *) expr;
+}
+
+/*
+ * make_andclause
+ *
+ * Creates an 'and' clause given a list of its subclauses.
+ */
+Expr *
+make_andclause(List *andclauses)
+{
+    BoolExpr   *expr = makeNode(BoolExpr);
+
+    expr->boolop = AND_EXPR;
+    expr->args = andclauses;
+    expr->location = -1;
+    return (Expr *) expr;
+}
+
+/*
+ * make_orclause
+ *
+ * Creates an 'or' clause given a list of its subclauses.
+ */
+Expr *
+make_orclause(List *orclauses)
+{
+    BoolExpr   *expr = makeNode(BoolExpr);
+
+    expr->boolop = OR_EXPR;
+    expr->args = orclauses;
+    expr->location = -1;
+    return (Expr *) expr;
+}
+
+/*
+ * make_notclause
+ *
+ * Create a 'not' clause given the expression to be negated.
+ */
+Expr *
+make_notclause(Expr *notclause)
+{
+    BoolExpr   *expr = makeNode(BoolExpr);
+
+    expr->boolop = NOT_EXPR;
+    expr->args = list_make1(notclause);
+    expr->location = -1;
+    return (Expr *) expr;
+}
+
+/*
+ * make_and_qual
+ *
+ * Variant of make_andclause for ANDing two qual conditions together.
+ * Qual conditions have the property that a NULL nodetree is interpreted
+ * as 'true'.
+ *
+ * NB: this makes no attempt to preserve AND/OR flatness; so it should not
+ * be used on a qual that has already been run through prepqual.c.
+ */
+Node *
+make_and_qual(Node *qual1, Node *qual2)
+{
+    if (qual1 == NULL)
+        return qual2;
+    if (qual2 == NULL)
+        return qual1;
+    return (Node *) make_andclause(list_make2(qual1, qual2));
+}
+
+/*
+ * The planner and executor usually represent qualification expressions
+ * as lists of boolean expressions with implicit AND semantics.
+ *
+ * These functions convert between an AND-semantics expression list and the
+ * ordinary representation of a boolean expression.
+ *
+ * Note that an empty list is considered equivalent to TRUE.
+ */
+Expr *
+make_ands_explicit(List *andclauses)
+{
+    if (andclauses == NIL)
+        return (Expr *) makeBoolConst(true, false);
+    else if (list_length(andclauses) == 1)
+        return (Expr *) linitial(andclauses);
+    else
+        return make_andclause(andclauses);
+}
+
+List *
+make_ands_implicit(Expr *clause)
+{
+    /*
+     * NB: because the parser sets the qual field to NULL in a query that has
+     * no WHERE clause, we must consider a NULL input clause as TRUE, even
+     * though one might more reasonably think it FALSE.
+     */
+    if (clause == NULL)
+        return NIL;                /* NULL -> NIL list == TRUE */
+    else if (is_andclause(clause))
+        return ((BoolExpr *) clause)->args;
+    else if (IsA(clause, Const) &&
+             !((Const *) clause)->constisnull &&
+             DatumGetBool(((Const *) clause)->constvalue))
+        return NIL;                /* constant TRUE input -> NIL list */
+    else
+        return list_make1(clause);
+}
+
+/*
  * makeGroupingSet
  *
  */
diff --git a/src/backend/nodes/print.c b/src/backend/nodes/print.c
index 0278108..c5b6a0b 100644
--- a/src/backend/nodes/print.c
+++ b/src/backend/nodes/print.c
@@ -21,8 +21,9 @@

 #include "access/printtup.h"
 #include "lib/stringinfo.h"
+#include "nodes/nodeFuncs.h"
 #include "nodes/print.h"
-#include "optimizer/clauses.h"
+#include "nodes/relation.h"
 #include "parser/parsetree.h"
 #include "utils/lsyscache.h"

diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index 3739b98..5045270 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -15,6 +15,7 @@
 #include "postgres.h"

 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/pathnode.h"
@@ -688,7 +689,7 @@ clause_selectivity(PlannerInfo *root,
             /* XXX any way to do better than default? */
         }
     }
-    else if (not_clause(clause))
+    else if (is_notclause(clause))
     {
         /* inverse of the selectivity of the underlying clause */
         s1 = 1.0 - clause_selectivity(root,
@@ -697,7 +698,7 @@ clause_selectivity(PlannerInfo *root,
                                       jointype,
                                       sjinfo);
     }
-    else if (and_clause(clause))
+    else if (is_andclause(clause))
     {
         /* share code with clauselist_selectivity() */
         s1 = clauselist_selectivity(root,
@@ -706,7 +707,7 @@ clause_selectivity(PlannerInfo *root,
                                     jointype,
                                     sjinfo);
     }
-    else if (or_clause(clause))
+    else if (is_orclause(clause))
     {
         /*
          * Selectivities for an OR clause are computed as s1+s2 - s1*s2 to
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 99c5ad9..e288804 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -79,6 +79,7 @@
 #include "executor/executor.h"
 #include "executor/nodeHash.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index f8e674c..be50d56 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -1297,7 +1297,7 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
             List       *indlist;

             /* OR arguments should be ANDs or sub-RestrictInfos */
-            if (and_clause(orarg))
+            if (is_andclause(orarg))
             {
                 List       *andargs = ((BoolExpr *) orarg)->args;

@@ -3368,7 +3368,7 @@ match_boolean_index_clause(Node *clause,
     if (match_index_to_operand(clause, indexcol, index))
         return true;
     /* NOT clause? */
-    if (not_clause(clause))
+    if (is_notclause(clause))
     {
         if (match_index_to_operand((Node *) get_notclausearg((Expr *) clause),
                                    indexcol, index))
@@ -3680,7 +3680,7 @@ expand_boolean_index_clause(Node *clause,
                              InvalidOid, InvalidOid);
     }
     /* NOT clause? */
-    if (not_clause(clause))
+    if (is_notclause(clause))
     {
         Node       *arg = (Node *) get_notclausearg((Expr *) clause);

diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 8bfe9c3..dfbbfda 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -15,6 +15,7 @@
 #include "postgres.h"

 #include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/joininfo.h"
@@ -1554,8 +1555,7 @@ have_partkey_equi_join(RelOptInfo *joinrel,
         if (!rinfo->mergeopfamilies && !OidIsValid(rinfo->hashjoinoperator))
             continue;

-        opexpr = (OpExpr *) rinfo->clause;
-        Assert(is_opclause(opexpr));
+        opexpr = castNode(OpExpr, rinfo->clause);

         /*
          * The equi-join between partition keys is strict if equi-join between
diff --git a/src/backend/optimizer/path/tidpath.c b/src/backend/optimizer/path/tidpath.c
index 74b5a15..94c88bd 100644
--- a/src/backend/optimizer/path/tidpath.c
+++ b/src/backend/optimizer/path/tidpath.c
@@ -250,7 +250,7 @@ TidQualFromRestrictInfoList(List *rlist, RelOptInfo *rel)
                 List       *sublist;

                 /* OR arguments should be ANDs or sub-RestrictInfos */
-                if (and_clause(orarg))
+                if (is_andclause(orarg))
                 {
                     List       *andargs = ((BoolExpr *) orarg)->args;

diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index a663740..06ed32a 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -16,6 +16,7 @@

 #include "catalog/pg_type.h"
 #include "catalog/pg_class.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 64272dd..da65d04 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -739,7 +739,7 @@ testexpr_is_hashable(Node *testexpr)
         if (hash_ok_operator((OpExpr *) testexpr))
             return true;
     }
-    else if (and_clause(testexpr))
+    else if (is_andclause(testexpr))
     {
         ListCell   *l;

@@ -1694,7 +1694,7 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context)
      * propagates down in both cases.  (Note that this is unlike the meaning
      * of "top level qual" used in most other places in Postgres.)
      */
-    if (and_clause(node))
+    if (is_andclause(node))
     {
         List       *newargs = NIL;
         ListCell   *l;
@@ -1707,7 +1707,7 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context)
             Node       *newarg;

             newarg = process_sublinks_mutator(lfirst(l), &locContext);
-            if (and_clause(newarg))
+            if (is_andclause(newarg))
                 newargs = list_concat(newargs, ((BoolExpr *) newarg)->args);
             else
                 newargs = lappend(newargs, newarg);
@@ -1715,7 +1715,7 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context)
         return (Node *) make_andclause(newargs);
     }

-    if (or_clause(node))
+    if (is_orclause(node))
     {
         List       *newargs = NIL;
         ListCell   *l;
@@ -1728,7 +1728,7 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context)
             Node       *newarg;

             newarg = process_sublinks_mutator(lfirst(l), &locContext);
-            if (or_clause(newarg))
+            if (is_orclause(newarg))
                 newargs = list_concat(newargs, ((BoolExpr *) newarg)->args);
             else
                 newargs = lappend(newargs, newarg);
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 77dbf4e..3bef60a 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -452,7 +452,7 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node,
         /* Else return it unmodified */
         return node;
     }
-    if (not_clause(node))
+    if (is_notclause(node))
     {
         /* If the immediate argument of NOT is EXISTS, try to convert */
         SubLink    *sublink = (SubLink *) get_notclausearg((Expr *) node);
@@ -519,7 +519,7 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node,
         /* Else return it unmodified */
         return node;
     }
-    if (and_clause(node))
+    if (is_andclause(node))
     {
         /* Recurse into AND clause */
         List       *newclauses = NIL;
diff --git a/src/backend/optimizer/prep/prepqual.c b/src/backend/optimizer/prep/prepqual.c
index 234dc5b..2bd6c20 100644
--- a/src/backend/optimizer/prep/prepqual.c
+++ b/src/backend/optimizer/prep/prepqual.c
@@ -32,6 +32,7 @@
 #include "postgres.h"

 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/prep.h"
 #include "utils/lsyscache.h"
@@ -333,7 +334,7 @@ pull_ands(List *andlist)
          * built a new arglist not shared with any other expr. Otherwise we'd
          * need a list_copy here.
          */
-        if (and_clause(subexpr))
+        if (is_andclause(subexpr))
             out_list = list_concat(out_list,
                                    pull_ands(((BoolExpr *) subexpr)->args));
         else
@@ -365,7 +366,7 @@ pull_ors(List *orlist)
          * built a new arglist not shared with any other expr. Otherwise we'd
          * need a list_copy here.
          */
-        if (or_clause(subexpr))
+        if (is_orclause(subexpr))
             out_list = list_concat(out_list,
                                    pull_ors(((BoolExpr *) subexpr)->args));
         else
@@ -415,7 +416,7 @@ pull_ors(List *orlist)
 static Expr *
 find_duplicate_ors(Expr *qual, bool is_check)
 {
-    if (or_clause((Node *) qual))
+    if (is_orclause(qual))
     {
         List       *orlist = NIL;
         ListCell   *temp;
@@ -459,7 +460,7 @@ find_duplicate_ors(Expr *qual, bool is_check)
         /* Now we can look for duplicate ORs */
         return process_duplicate_ors(orlist);
     }
-    else if (and_clause((Node *) qual))
+    else if (is_andclause(qual))
     {
         List       *andlist = NIL;
         ListCell   *temp;
@@ -550,7 +551,7 @@ process_duplicate_ors(List *orlist)
     {
         Expr       *clause = (Expr *) lfirst(temp);

-        if (and_clause((Node *) clause))
+        if (is_andclause(clause))
         {
             List       *subclauses = ((BoolExpr *) clause)->args;
             int            nclauses = list_length(subclauses);
@@ -588,7 +589,7 @@ process_duplicate_ors(List *orlist)
         {
             Expr       *clause = (Expr *) lfirst(temp2);

-            if (and_clause((Node *) clause))
+            if (is_andclause(clause))
             {
                 if (!list_member(((BoolExpr *) clause)->args, refclause))
                 {
@@ -631,7 +632,7 @@ process_duplicate_ors(List *orlist)
     {
         Expr       *clause = (Expr *) lfirst(temp);

-        if (and_clause((Node *) clause))
+        if (is_andclause(clause))
         {
             List       *subclauses = ((BoolExpr *) clause)->args;

diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 94b8fa0..816aa86 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -158,244 +158,6 @@ static bool tlist_matches_coltypelist(List *tlist, List *coltypelist);


 /*****************************************************************************
- *        OPERATOR clause functions
- *****************************************************************************/
-
-/*
- * make_opclause
- *      Creates an operator clause given its operator info, left operand
- *      and right operand (pass NULL to create single-operand clause),
- *      and collation info.
- */
-Expr *
-make_opclause(Oid opno, Oid opresulttype, bool opretset,
-              Expr *leftop, Expr *rightop,
-              Oid opcollid, Oid inputcollid)
-{
-    OpExpr       *expr = makeNode(OpExpr);
-
-    expr->opno = opno;
-    expr->opfuncid = InvalidOid;
-    expr->opresulttype = opresulttype;
-    expr->opretset = opretset;
-    expr->opcollid = opcollid;
-    expr->inputcollid = inputcollid;
-    if (rightop)
-        expr->args = list_make2(leftop, rightop);
-    else
-        expr->args = list_make1(leftop);
-    expr->location = -1;
-    return (Expr *) expr;
-}
-
-/*
- * get_leftop
- *
- * Returns the left operand of a clause of the form (op expr expr)
- *        or (op expr)
- */
-Node *
-get_leftop(const Expr *clause)
-{
-    const OpExpr *expr = (const OpExpr *) clause;
-
-    if (expr->args != NIL)
-        return linitial(expr->args);
-    else
-        return NULL;
-}
-
-/*
- * get_rightop
- *
- * Returns the right operand in a clause of the form (op expr expr).
- * NB: result will be NULL if applied to a unary op clause.
- */
-Node *
-get_rightop(const Expr *clause)
-{
-    const OpExpr *expr = (const OpExpr *) clause;
-
-    if (list_length(expr->args) >= 2)
-        return lsecond(expr->args);
-    else
-        return NULL;
-}
-
-/*****************************************************************************
- *        NOT clause functions
- *****************************************************************************/
-
-/*
- * not_clause
- *
- * Returns t iff this is a 'not' clause: (NOT expr).
- */
-bool
-not_clause(Node *clause)
-{
-    return (clause != NULL &&
-            IsA(clause, BoolExpr) &&
-            ((BoolExpr *) clause)->boolop == NOT_EXPR);
-}
-
-/*
- * make_notclause
- *
- * Create a 'not' clause given the expression to be negated.
- */
-Expr *
-make_notclause(Expr *notclause)
-{
-    BoolExpr   *expr = makeNode(BoolExpr);
-
-    expr->boolop = NOT_EXPR;
-    expr->args = list_make1(notclause);
-    expr->location = -1;
-    return (Expr *) expr;
-}
-
-/*
- * get_notclausearg
- *
- * Retrieve the clause within a 'not' clause
- */
-Expr *
-get_notclausearg(Expr *notclause)
-{
-    return linitial(((BoolExpr *) notclause)->args);
-}
-
-/*****************************************************************************
- *        OR clause functions
- *****************************************************************************/
-
-/*
- * or_clause
- *
- * Returns t iff the clause is an 'or' clause: (OR { expr }).
- */
-bool
-or_clause(Node *clause)
-{
-    return (clause != NULL &&
-            IsA(clause, BoolExpr) &&
-            ((BoolExpr *) clause)->boolop == OR_EXPR);
-}
-
-/*
- * make_orclause
- *
- * Creates an 'or' clause given a list of its subclauses.
- */
-Expr *
-make_orclause(List *orclauses)
-{
-    BoolExpr   *expr = makeNode(BoolExpr);
-
-    expr->boolop = OR_EXPR;
-    expr->args = orclauses;
-    expr->location = -1;
-    return (Expr *) expr;
-}
-
-/*****************************************************************************
- *        AND clause functions
- *****************************************************************************/
-
-
-/*
- * and_clause
- *
- * Returns t iff its argument is an 'and' clause: (AND { expr }).
- */
-bool
-and_clause(Node *clause)
-{
-    return (clause != NULL &&
-            IsA(clause, BoolExpr) &&
-            ((BoolExpr *) clause)->boolop == AND_EXPR);
-}
-
-/*
- * make_andclause
- *
- * Creates an 'and' clause given a list of its subclauses.
- */
-Expr *
-make_andclause(List *andclauses)
-{
-    BoolExpr   *expr = makeNode(BoolExpr);
-
-    expr->boolop = AND_EXPR;
-    expr->args = andclauses;
-    expr->location = -1;
-    return (Expr *) expr;
-}
-
-/*
- * make_and_qual
- *
- * Variant of make_andclause for ANDing two qual conditions together.
- * Qual conditions have the property that a NULL nodetree is interpreted
- * as 'true'.
- *
- * NB: this makes no attempt to preserve AND/OR flatness; so it should not
- * be used on a qual that has already been run through prepqual.c.
- */
-Node *
-make_and_qual(Node *qual1, Node *qual2)
-{
-    if (qual1 == NULL)
-        return qual2;
-    if (qual2 == NULL)
-        return qual1;
-    return (Node *) make_andclause(list_make2(qual1, qual2));
-}
-
-/*
- * The planner frequently prefers to represent qualification expressions
- * as lists of boolean expressions with implicit AND semantics.
- *
- * These functions convert between an AND-semantics expression list and the
- * ordinary representation of a boolean expression.
- *
- * Note that an empty list is considered equivalent to TRUE.
- */
-Expr *
-make_ands_explicit(List *andclauses)
-{
-    if (andclauses == NIL)
-        return (Expr *) makeBoolConst(true, false);
-    else if (list_length(andclauses) == 1)
-        return (Expr *) linitial(andclauses);
-    else
-        return make_andclause(andclauses);
-}
-
-List *
-make_ands_implicit(Expr *clause)
-{
-    /*
-     * NB: because the parser sets the qual field to NULL in a query that has
-     * no WHERE clause, we must consider a NULL input clause as TRUE, even
-     * though one might more reasonably think it FALSE.  Grumble. If this
-     * causes trouble, consider changing the parser's behavior.
-     */
-    if (clause == NULL)
-        return NIL;                /* NULL -> NIL list == TRUE */
-    else if (and_clause((Node *) clause))
-        return ((BoolExpr *) clause)->args;
-    else if (IsA(clause, Const) &&
-             !((Const *) clause)->constisnull &&
-             DatumGetBool(((Const *) clause)->constvalue))
-        return NIL;                /* constant TRUE input -> NIL list */
-    else
-        return list_make1(clause);
-}
-
-
-/*****************************************************************************
  *        Aggregate-function clause manipulation
  *****************************************************************************/

@@ -3962,7 +3724,7 @@ simplify_or_arguments(List *args,
         unprocessed_args = list_delete_first(unprocessed_args);

         /* flatten nested ORs as per above comment */
-        if (or_clause(arg))
+        if (is_orclause(arg))
         {
             List       *subargs = list_copy(((BoolExpr *) arg)->args);

@@ -3988,7 +3750,7 @@ simplify_or_arguments(List *args,
          * since it's not a mainstream case. In particular we don't worry
          * about const-simplifying the input twice.
          */
-        if (or_clause(arg))
+        if (is_orclause(arg))
         {
             List       *subargs = list_copy(((BoolExpr *) arg)->args);

@@ -4064,7 +3826,7 @@ simplify_and_arguments(List *args,
         unprocessed_args = list_delete_first(unprocessed_args);

         /* flatten nested ANDs as per above comment */
-        if (and_clause(arg))
+        if (is_andclause(arg))
         {
             List       *subargs = list_copy(((BoolExpr *) arg)->args);

@@ -4090,7 +3852,7 @@ simplify_and_arguments(List *args,
          * since it's not a mainstream case. In particular we don't worry
          * about const-simplifying the input twice.
          */
-        if (and_clause(arg))
+        if (is_andclause(arg))
         {
             List       *subargs = list_copy(((BoolExpr *) arg)->args);

diff --git a/src/backend/optimizer/util/orclauses.c b/src/backend/optimizer/util/orclauses.c
index 9420f13..c62ba88 100644
--- a/src/backend/optimizer/util/orclauses.c
+++ b/src/backend/optimizer/util/orclauses.c
@@ -15,6 +15,8 @@

 #include "postgres.h"

+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/orclauses.h"
@@ -173,7 +175,7 @@ extract_or_clause(RestrictInfo *or_rinfo, RelOptInfo *rel)
      * selectivity and other cached data is computed exactly the same way for
      * a restriction clause as for a join clause, which seems undesirable.
      */
-    Assert(or_clause((Node *) or_rinfo->orclause));
+    Assert(is_orclause(or_rinfo->orclause));
     foreach(lc, ((BoolExpr *) or_rinfo->orclause)->args)
     {
         Node       *orarg = (Node *) lfirst(lc);
@@ -181,7 +183,7 @@ extract_or_clause(RestrictInfo *or_rinfo, RelOptInfo *rel)
         Node       *subclause;

         /* OR arguments should be ANDs or sub-RestrictInfos */
-        if (and_clause(orarg))
+        if (is_andclause(orarg))
         {
             List       *andargs = ((BoolExpr *) orarg)->args;
             ListCell   *lc2;
@@ -231,7 +233,7 @@ extract_or_clause(RestrictInfo *or_rinfo, RelOptInfo *rel)
          * to preserve AND/OR flatness (ie, no OR directly underneath OR).
          */
         subclause = (Node *) make_ands_explicit(subclauses);
-        if (or_clause(subclause))
+        if (is_orclause(subclause))
             clauselist = list_concat(clauselist,
                                      list_copy(((BoolExpr *) subclause)->args));
         else
diff --git a/src/backend/optimizer/util/predtest.c b/src/backend/optimizer/util/predtest.c
index 3d5ef69..ecbb0db 100644
--- a/src/backend/optimizer/util/predtest.c
+++ b/src/backend/optimizer/util/predtest.c
@@ -19,6 +19,7 @@
 #include "catalog/pg_type.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/predtest.h"
@@ -839,14 +840,14 @@ predicate_classify(Node *clause, PredIterInfo info)
     }

     /* Handle normal AND and OR boolean clauses */
-    if (and_clause(clause))
+    if (is_andclause(clause))
     {
         info->startup_fn = boolexpr_startup_fn;
         info->next_fn = list_next_fn;
         info->cleanup_fn = list_cleanup_fn;
         return CLASS_AND;
     }
-    if (or_clause(clause))
+    if (is_orclause(clause))
     {
         info->startup_fn = boolexpr_startup_fn;
         info->next_fn = list_next_fn;
diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c
index e633881..c1bda81 100644
--- a/src/backend/optimizer/util/restrictinfo.c
+++ b/src/backend/optimizer/util/restrictinfo.c
@@ -14,6 +14,8 @@
  */
 #include "postgres.h"

+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/var.h"
@@ -67,7 +69,7 @@ make_restrictinfo(Expr *clause,
      * If it's an OR clause, build a modified copy with RestrictInfos inserted
      * above each subclause of the top-level AND/OR structure.
      */
-    if (or_clause((Node *) clause))
+    if (is_orclause(clause))
         return (RestrictInfo *) make_sub_restrictinfos(clause,
                                                        is_pushed_down,
                                                        outerjoin_delayed,
@@ -78,7 +80,7 @@ make_restrictinfo(Expr *clause,
                                                        nullable_relids);

     /* Shouldn't be an AND clause, else AND/OR flattening messed up */
-    Assert(!and_clause((Node *) clause));
+    Assert(!is_andclause(clause));

     return make_restrictinfo_internal(clause,
                                       NULL,
@@ -232,7 +234,7 @@ make_sub_restrictinfos(Expr *clause,
                        Relids outer_relids,
                        Relids nullable_relids)
 {
-    if (or_clause((Node *) clause))
+    if (is_orclause(clause))
     {
         List       *orlist = NIL;
         ListCell   *temp;
@@ -257,7 +259,7 @@ make_sub_restrictinfos(Expr *clause,
                                                    outer_relids,
                                                    nullable_relids);
     }
-    else if (and_clause((Node *) clause))
+    else if (is_andclause(clause))
     {
         List       *andlist = NIL;
         ListCell   *temp;
diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c
index 901433c..f5155fe 100644
--- a/src/backend/partitioning/partprune.c
+++ b/src/backend/partitioning/partprune.c
@@ -775,7 +775,7 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context,
              * independently, collect their step IDs to be stored in the
              * combine step we'll be creating.
              */
-            if (or_clause((Node *) clause))
+            if (is_orclause(clause))
             {
                 List       *arg_stepids = NIL;
                 bool        all_args_contradictory = true;
@@ -865,7 +865,7 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context,
                 }
                 continue;
             }
-            else if (and_clause((Node *) clause))
+            else if (is_andclause(clause))
             {
                 List       *args = ((BoolExpr *) clause)->args;
                 List       *argsteps,
@@ -3262,7 +3262,7 @@ match_boolean_partition_clause(Oid partopfamily, Expr *clause, Expr *partkey,
     }
     else
     {
-        bool        is_not_clause = not_clause((Node *) clause);
+        bool        is_not_clause = is_notclause(clause);

         leftop = is_not_clause ? get_notclausearg(clause) : clause;

diff --git a/src/backend/statistics/dependencies.c b/src/backend/statistics/dependencies.c
index 1d7e028..a8e9f72 100644
--- a/src/backend/statistics/dependencies.c
+++ b/src/backend/statistics/dependencies.c
@@ -18,6 +18,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic_ext.h"
 #include "lib/stringinfo.h"
+#include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/var.h"
@@ -802,7 +803,7 @@ dependency_is_compatible_clause(Node *clause, Index relid, AttrNumber *attnum)

         /* OK to proceed with checking "var" */
     }
-    else if (not_clause((Node *) rinfo->clause))
+    else if (is_notclause(rinfo->clause))
     {
         /*
          * "NOT x" can be interpreted as "x = false", so get the argument and
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index bcf4f10..12bba1b 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -69,6 +69,7 @@
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/prep.h"
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 5b5aa2d..aa25fab 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -80,6 +80,18 @@ extern FuncExpr *makeFuncExpr(Oid funcid, Oid rettype, List *args,

 extern FuncCall *makeFuncCall(List *name, List *args, int location);

+extern Expr *make_opclause(Oid opno, Oid opresulttype, bool opretset,
+              Expr *leftop, Expr *rightop,
+              Oid opcollid, Oid inputcollid);
+
+extern Expr *make_andclause(List *andclauses);
+extern Expr *make_orclause(List *orclauses);
+extern Expr *make_notclause(Expr *notclause);
+
+extern Node *make_and_qual(Node *qual1, Node *qual2);
+extern Expr *make_ands_explicit(List *andclauses);
+extern List *make_ands_implicit(Expr *clause);
+
 extern DefElem *makeDefElem(char *name, Node *arg, int location);
 extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
                     DefElemAction defaction, int location);
diff --git a/src/include/nodes/nodeFuncs.h b/src/include/nodes/nodeFuncs.h
index a9f76bb..9875934 100644
--- a/src/include/nodes/nodeFuncs.h
+++ b/src/include/nodes/nodeFuncs.h
@@ -50,6 +50,78 @@ extern void fix_opfuncids(Node *node);
 extern void set_opfuncid(OpExpr *opexpr);
 extern void set_sa_opfuncid(ScalarArrayOpExpr *opexpr);

+/* Is clause a FuncExpr clause? */
+static inline bool
+is_funcclause(const void *clause)
+{
+    return clause != NULL && IsA(clause, FuncExpr);
+}
+
+/* Is clause an OpExpr clause? */
+static inline bool
+is_opclause(const void *clause)
+{
+    return clause != NULL && IsA(clause, OpExpr);
+}
+
+/* Extract left arg of a binary opclause, or only arg of a unary opclause */
+static inline Node *
+get_leftop(const void *clause)
+{
+    const OpExpr *expr = (const OpExpr *) clause;
+
+    if (expr->args != NIL)
+        return (Node *) linitial(expr->args);
+    else
+        return NULL;
+}
+
+/* Extract right arg of a binary opclause (NULL if it's a unary opclause) */
+static inline Node *
+get_rightop(const void *clause)
+{
+    const OpExpr *expr = (const OpExpr *) clause;
+
+    if (list_length(expr->args) >= 2)
+        return (Node *) lsecond(expr->args);
+    else
+        return NULL;
+}
+
+/* Is clause an AND clause? */
+static inline bool
+is_andclause(const void *clause)
+{
+    return (clause != NULL &&
+            IsA(clause, BoolExpr) &&
+            ((const BoolExpr *) clause)->boolop == AND_EXPR);
+}
+
+/* Is clause an OR clause? */
+static inline bool
+is_orclause(const void *clause)
+{
+    return (clause != NULL &&
+            IsA(clause, BoolExpr) &&
+            ((const BoolExpr *) clause)->boolop == OR_EXPR);
+}
+
+/* Is clause a NOT clause? */
+static inline bool
+is_notclause(const void *clause)
+{
+    return (clause != NULL &&
+            IsA(clause, BoolExpr) &&
+            ((const BoolExpr *) clause)->boolop == NOT_EXPR);
+}
+
+/* Extract argument from a clause known to be a NOT clause */
+static inline Expr *
+get_notclausearg(const void *notclause)
+{
+    return (Expr *) linitial(((const BoolExpr *) notclause)->args);
+}
+
 extern bool check_functions_in_node(Node *node, check_function_callback checker,
                         void *context);

diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 6891d0d..3f53428 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -17,9 +17,6 @@
 #include "access/htup.h"
 #include "nodes/relation.h"

-#define is_opclause(clause)        ((clause) != NULL && IsA(clause, OpExpr))
-#define is_funcclause(clause)    ((clause) != NULL && IsA(clause, FuncExpr))
-
 typedef struct
 {
     int            numWindowFuncs; /* total number of WindowFuncs found */
@@ -27,25 +24,6 @@ typedef struct
     List      **windowFuncs;    /* lists of WindowFuncs for each winref */
 } WindowFuncLists;

-extern Expr *make_opclause(Oid opno, Oid opresulttype, bool opretset,
-              Expr *leftop, Expr *rightop,
-              Oid opcollid, Oid inputcollid);
-extern Node *get_leftop(const Expr *clause);
-extern Node *get_rightop(const Expr *clause);
-
-extern bool not_clause(Node *clause);
-extern Expr *make_notclause(Expr *notclause);
-extern Expr *get_notclausearg(Expr *notclause);
-
-extern bool or_clause(Node *clause);
-extern Expr *make_orclause(List *orclauses);
-
-extern bool and_clause(Node *clause);
-extern Expr *make_andclause(List *andclauses);
-extern Node *make_and_qual(Node *qual1, Node *qual2);
-extern Expr *make_ands_explicit(List *andclauses);
-extern List *make_ands_implicit(Expr *clause);
-
 extern bool contain_agg_clause(Node *clause);
 extern void get_agg_clause_costs(PlannerInfo *root, Node *clause,
                      AggSplit aggsplit, AggClauseCosts *costs);
diff --git a/src/test/modules/test_predtest/test_predtest.c b/src/test/modules/test_predtest/test_predtest.c
index 6c9ed9d..c03748c 100644
--- a/src/test/modules/test_predtest/test_predtest.c
+++ b/src/test/modules/test_predtest/test_predtest.c
@@ -17,6 +17,7 @@
 #include "catalog/pg_type.h"
 #include "executor/spi.h"
 #include "funcapi.h"
+#include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/predtest.h"
 #include "utils/builtins.h"
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 4465f00..3304edb 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -836,7 +836,8 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
              */
             if (rte->lateral && root->hasJoinRTEs)
                 rte->subquery = (Query *)
-                    flatten_join_alias_vars(root, (Node *) rte->subquery);
+                    flatten_join_alias_vars(root->parse,
+                                            (Node *) rte->subquery);
         }
         else if (rte->rtekind == RTE_FUNCTION)
         {
@@ -1033,7 +1034,7 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind)
           kind == EXPRKIND_VALUES ||
           kind == EXPRKIND_TABLESAMPLE ||
           kind == EXPRKIND_TABLEFUNC))
-        expr = flatten_join_alias_vars(root, expr);
+        expr = flatten_join_alias_vars(root->parse, expr);

     /*
      * Simplify constant expressions.
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 3bef60a..1f993d4 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -986,7 +986,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
      * maybe even in the rewriter; but for now let's just fix this case here.)
      */
     subquery->targetList = (List *)
-        flatten_join_alias_vars(subroot, (Node *) subquery->targetList);
+        flatten_join_alias_vars(subroot->parse, (Node *) subquery->targetList);

     /*
      * Adjust level-0 varnos in subquery so that we can append its rangetable
@@ -3031,11 +3031,11 @@ get_relids_in_jointree(Node *jtnode, bool include_joins)
  * get_relids_for_join: get set of base RT indexes making up a join
  */
 Relids
-get_relids_for_join(PlannerInfo *root, int joinrelid)
+get_relids_for_join(Query *query, int joinrelid)
 {
     Node       *jtnode;

-    jtnode = find_jointree_node_for_rel((Node *) root->parse->jointree,
+    jtnode = find_jointree_node_for_rel((Node *) query->jointree,
                                         joinrelid);
     if (!jtnode)
         elog(ERROR, "could not find join node %d", joinrelid);
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index 48175b7..5116d7f 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -60,7 +60,7 @@ typedef struct

 typedef struct
 {
-    PlannerInfo *root;
+    Query       *query;            /* outer Query */
     int            sublevels_up;
     bool        possible_sublink;    /* could aliases include a SubLink? */
     bool        inserted_sublink;    /* have we inserted a SubLink? */
@@ -78,7 +78,7 @@ static bool pull_var_clause_walker(Node *node,
                        pull_var_clause_context *context);
 static Node *flatten_join_alias_vars_mutator(Node *node,
                                 flatten_join_alias_vars_context *context);
-static Relids alias_relid_set(PlannerInfo *root, Relids relids);
+static Relids alias_relid_set(Query *query, Relids relids);


 /*
@@ -667,16 +667,16 @@ pull_var_clause_walker(Node *node, pull_var_clause_context *context)
  * subqueries).
  */
 Node *
-flatten_join_alias_vars(PlannerInfo *root, Node *node)
+flatten_join_alias_vars(Query *query, Node *node)
 {
     flatten_join_alias_vars_context context;

-    context.root = root;
+    context.query = query;
     context.sublevels_up = 0;
     /* flag whether join aliases could possibly contain SubLinks */
-    context.possible_sublink = root->parse->hasSubLinks;
+    context.possible_sublink = query->hasSubLinks;
     /* if hasSubLinks is already true, no need to work hard */
-    context.inserted_sublink = root->parse->hasSubLinks;
+    context.inserted_sublink = query->hasSubLinks;

     return flatten_join_alias_vars_mutator(node, &context);
 }
@@ -696,7 +696,7 @@ flatten_join_alias_vars_mutator(Node *node,
         /* No change unless Var belongs to a JOIN of the target level */
         if (var->varlevelsup != context->sublevels_up)
             return node;        /* no need to copy, really */
-        rte = rt_fetch(var->varno, context->root->parse->rtable);
+        rte = rt_fetch(var->varno, context->query->rtable);
         if (rte->rtekind != RTE_JOIN)
             return node;
         if (var->varattno == InvalidAttrNumber)
@@ -783,7 +783,7 @@ flatten_join_alias_vars_mutator(Node *node,
         /* now fix PlaceHolderVar's relid sets */
         if (phv->phlevelsup == context->sublevels_up)
         {
-            phv->phrels = alias_relid_set(context->root,
+            phv->phrels = alias_relid_set(context->query,
                                           phv->phrels);
         }
         return (Node *) phv;
@@ -823,7 +823,7 @@ flatten_join_alias_vars_mutator(Node *node,
  * underlying base relids
  */
 static Relids
-alias_relid_set(PlannerInfo *root, Relids relids)
+alias_relid_set(Query *query, Relids relids)
 {
     Relids        result = NULL;
     int            rtindex;
@@ -831,10 +831,10 @@ alias_relid_set(PlannerInfo *root, Relids relids)
     rtindex = -1;
     while ((rtindex = bms_next_member(relids, rtindex)) >= 0)
     {
-        RangeTblEntry *rte = rt_fetch(rtindex, root->parse->rtable);
+        RangeTblEntry *rte = rt_fetch(rtindex, query->rtable);

         if (rte->rtekind == RTE_JOIN)
-            result = bms_join(result, get_relids_for_join(root, rtindex));
+            result = bms_join(result, get_relids_for_join(query, rtindex));
         else
             result = bms_add_member(result, rtindex);
     }
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 8ed3816..5b73378 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -43,7 +43,7 @@ typedef struct
 {
     ParseState *pstate;
     Query       *qry;
-    PlannerInfo *root;
+    bool        hasJoinRTEs;
     List       *groupClauses;
     List       *groupClauseCommonVars;
     bool        have_non_var_grouping;
@@ -65,7 +65,7 @@ static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
 static bool check_ungrouped_columns_walker(Node *node,
                                check_ungrouped_columns_context *context);
 static void finalize_grouping_exprs(Node *node, ParseState *pstate, Query *qry,
-                        List *groupClauses, PlannerInfo *root,
+                        List *groupClauses, bool hasJoinRTEs,
                         bool have_non_var_grouping);
 static bool finalize_grouping_exprs_walker(Node *node,
                                check_ungrouped_columns_context *context);
@@ -1039,7 +1039,6 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
     ListCell   *l;
     bool        hasJoinRTEs;
     bool        hasSelfRefRTEs;
-    PlannerInfo *root = NULL;
     Node       *clause;

     /* This should only be called if we found aggregates or grouping */
@@ -1130,20 +1129,11 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
      * If there are join alias vars involved, we have to flatten them to the
      * underlying vars, so that aliased and unaliased vars will be correctly
      * taken as equal.  We can skip the expense of doing this if no rangetable
-     * entries are RTE_JOIN kind. We use the planner's flatten_join_alias_vars
-     * routine to do the flattening; it wants a PlannerInfo root node, which
-     * fortunately can be mostly dummy.
+     * entries are RTE_JOIN kind.
      */
     if (hasJoinRTEs)
-    {
-        root = makeNode(PlannerInfo);
-        root->parse = qry;
-        root->planner_cxt = CurrentMemoryContext;
-        root->hasJoinRTEs = true;
-
-        groupClauses = (List *) flatten_join_alias_vars(root,
+        groupClauses = (List *) flatten_join_alias_vars(qry,
                                                         (Node *) groupClauses);
-    }

     /*
      * Detect whether any of the grouping expressions aren't simple Vars; if
@@ -1183,10 +1173,10 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
      */
     clause = (Node *) qry->targetList;
     finalize_grouping_exprs(clause, pstate, qry,
-                            groupClauses, root,
+                            groupClauses, hasJoinRTEs,
                             have_non_var_grouping);
     if (hasJoinRTEs)
-        clause = flatten_join_alias_vars(root, clause);
+        clause = flatten_join_alias_vars(qry, clause);
     check_ungrouped_columns(clause, pstate, qry,
                             groupClauses, groupClauseCommonVars,
                             have_non_var_grouping,
@@ -1194,10 +1184,10 @@ parseCheckAggregates(ParseState *pstate, Query *qry)

     clause = (Node *) qry->havingQual;
     finalize_grouping_exprs(clause, pstate, qry,
-                            groupClauses, root,
+                            groupClauses, hasJoinRTEs,
                             have_non_var_grouping);
     if (hasJoinRTEs)
-        clause = flatten_join_alias_vars(root, clause);
+        clause = flatten_join_alias_vars(qry, clause);
     check_ungrouped_columns(clause, pstate, qry,
                             groupClauses, groupClauseCommonVars,
                             have_non_var_grouping,
@@ -1245,7 +1235,7 @@ check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,

     context.pstate = pstate;
     context.qry = qry;
-    context.root = NULL;
+    context.hasJoinRTEs = false;    /* assume caller flattened join Vars */
     context.groupClauses = groupClauses;
     context.groupClauseCommonVars = groupClauseCommonVars;
     context.have_non_var_grouping = have_non_var_grouping;
@@ -1445,14 +1435,14 @@ check_ungrouped_columns_walker(Node *node,
  */
 static void
 finalize_grouping_exprs(Node *node, ParseState *pstate, Query *qry,
-                        List *groupClauses, PlannerInfo *root,
+                        List *groupClauses, bool hasJoinRTEs,
                         bool have_non_var_grouping)
 {
     check_ungrouped_columns_context context;

     context.pstate = pstate;
     context.qry = qry;
-    context.root = root;
+    context.hasJoinRTEs = hasJoinRTEs;
     context.groupClauses = groupClauses;
     context.groupClauseCommonVars = NIL;
     context.have_non_var_grouping = have_non_var_grouping;
@@ -1525,8 +1515,8 @@ finalize_grouping_exprs_walker(Node *node,
                 Node       *expr = lfirst(lc);
                 Index        ref = 0;

-                if (context->root)
-                    expr = flatten_join_alias_vars(context->root, expr);
+                if (context->hasJoinRTEs)
+                    expr = flatten_join_alias_vars(context->qry, expr);

                 /*
                  * Each expression must match a grouping entry at the current
diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h
index 62d45dd..3ad8dcb 100644
--- a/src/include/optimizer/prep.h
+++ b/src/include/optimizer/prep.h
@@ -27,7 +27,7 @@ extern void pull_up_subqueries(PlannerInfo *root);
 extern void flatten_simple_union_all(PlannerInfo *root);
 extern void reduce_outer_joins(PlannerInfo *root);
 extern Relids get_relids_in_jointree(Node *jtnode, bool include_joins);
-extern Relids get_relids_for_join(PlannerInfo *root, int joinrelid);
+extern Relids get_relids_for_join(Query *query, int joinrelid);

 /*
  * prototypes for prepqual.c
diff --git a/src/include/optimizer/var.h b/src/include/optimizer/var.h
index a0c1387..f975074 100644
--- a/src/include/optimizer/var.h
+++ b/src/include/optimizer/var.h
@@ -35,6 +35,6 @@ extern bool contain_var_clause(Node *node);
 extern bool contain_vars_of_level(Node *node, int levelsup);
 extern int    locate_var_of_level(Node *node, int levelsup);
 extern List *pull_var_clause(Node *node, int flags);
-extern Node *flatten_join_alias_vars(PlannerInfo *root, Node *node);
+extern Node *flatten_join_alias_vars(Query *query, Node *node);

 #endif                            /* VAR_H */
diff --git a/contrib/bloom/blcost.c b/contrib/bloom/blcost.c
index 902d23e..2d8a7f1 100644
--- a/contrib/bloom/blcost.c
+++ b/contrib/bloom/blcost.c
@@ -13,7 +13,6 @@
 #include "postgres.h"

 #include "fmgr.h"
-#include "optimizer/cost.h"
 #include "utils/selfuncs.h"

 #include "bloom.h"
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index e7db83d..be626be 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -29,11 +29,10 @@
 #include "foreign/foreign.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
-#include "optimizer/cost.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/planmain.h"
 #include "optimizer/restrictinfo.h"
-#include "optimizer/var.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
 #include "utils/sampling.h"
diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index f368d4b..b0e44e5 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -48,10 +48,9 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/plannodes.h"
-#include "optimizer/clauses.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/prep.h"
 #include "optimizer/tlist.h"
-#include "optimizer/var.h"
 #include "parser/parsetree.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 9244fe7..f753fff 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -28,11 +28,11 @@
 #include "nodes/nodeFuncs.h"
 #include "optimizer/cost.h"
 #include "optimizer/clauses.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
 #include "optimizer/restrictinfo.h"
-#include "optimizer/var.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
 #include "utils/builtins.h"
diff --git a/contrib/tsm_system_rows/tsm_system_rows.c b/contrib/tsm_system_rows/tsm_system_rows.c
index 69944f6..c92490f 100644
--- a/contrib/tsm_system_rows/tsm_system_rows.c
+++ b/contrib/tsm_system_rows/tsm_system_rows.c
@@ -33,8 +33,7 @@
 #include "access/tsmapi.h"
 #include "catalog/pg_type.h"
 #include "miscadmin.h"
-#include "optimizer/clauses.h"
-#include "optimizer/cost.h"
+#include "optimizer/optimizer.h"
 #include "utils/sampling.h"

 PG_MODULE_MAGIC;
diff --git a/contrib/tsm_system_time/tsm_system_time.c b/contrib/tsm_system_time/tsm_system_time.c
index f516552..edeacf0 100644
--- a/contrib/tsm_system_time/tsm_system_time.c
+++ b/contrib/tsm_system_time/tsm_system_time.c
@@ -31,8 +31,7 @@
 #include "access/tsmapi.h"
 #include "catalog/pg_type.h"
 #include "miscadmin.h"
-#include "optimizer/clauses.h"
-#include "optimizer/cost.h"
+#include "optimizer/optimizer.h"
 #include "utils/sampling.h"
 #include "utils/spccache.h"

diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 94463ff..bd142a3 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -22,7 +22,7 @@
 #include "access/xloginsert.h"
 #include "catalog/index.h"
 #include "miscadmin.h"
-#include "optimizer/cost.h"
+#include "optimizer/optimizer.h"
 #include "storage/bufmgr.h"
 #include "storage/smgr.h"
 #include "utils/memutils.h"
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index de147d7..8e63c1f 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -22,7 +22,6 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/pg_amop.h"
-#include "optimizer/paths.h"
 #include "storage/bufmgr.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -32,9 +31,6 @@
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"

-extern Expr *spgcanorderbyop(IndexOptInfo *index,
-                PathKey *pathkey, int pathkeyno,
-                Expr *orderby_clause, int *indexcol_p);

 /*
  * SP-GiST handler function: return IndexAmRoutine with access method parameters
diff --git a/src/backend/access/tablesample/bernoulli.c b/src/backend/access/tablesample/bernoulli.c
index 5e5e32d..9360b5b 100644
--- a/src/backend/access/tablesample/bernoulli.c
+++ b/src/backend/access/tablesample/bernoulli.c
@@ -29,8 +29,7 @@
 #include "access/hash.h"
 #include "access/tsmapi.h"
 #include "catalog/pg_type.h"
-#include "optimizer/clauses.h"
-#include "optimizer/cost.h"
+#include "optimizer/optimizer.h"
 #include "utils/builtins.h"


diff --git a/src/backend/access/tablesample/system.c b/src/backend/access/tablesample/system.c
index 383387b..298e0ab 100644
--- a/src/backend/access/tablesample/system.c
+++ b/src/backend/access/tablesample/system.c
@@ -31,8 +31,7 @@
 #include "access/relscan.h"
 #include "access/tsmapi.h"
 #include "catalog/pg_type.h"
-#include "optimizer/clauses.h"
-#include "optimizer/cost.h"
+#include "optimizer/optimizer.h"
 #include "utils/builtins.h"


diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index 9c55c20..ce2b616 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -28,7 +28,7 @@
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
-#include "optimizer/planmain.h"
+#include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/ipc.h"
 #include "storage/sinval.h"
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index cc865de..910f651 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -64,9 +64,7 @@
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
-#include "optimizer/clauses.h"
-#include "optimizer/var.h"
-#include "optimizer/planner.h"
+#include "optimizer/optimizer.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
 #include "parser/parse_expr.h"
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 225c078..169b2de 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -56,8 +56,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
-#include "optimizer/clauses.h"
-#include "optimizer/planner.h"
+#include "optimizer/optimizer.h"
 #include "parser/parser.h"
 #include "rewrite/rewriteManip.h"
 #include "storage/bufmgr.h"
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 62d1ec6..0d3bc3a 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -24,9 +24,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_partitioned_table.h"
 #include "nodes/makefuncs.h"
-#include "optimizer/clauses.h"
-#include "optimizer/prep.h"
-#include "optimizer/var.h"
+#include "optimizer/optimizer.h"
 #include "partitioning/partbounds.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/fmgroids.h"
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index ec1d6b6..a74af4c 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -38,7 +38,7 @@
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "miscadmin.h"
-#include "optimizer/planner.h"
+#include "optimizer/optimizer.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 1c90934..86f2bf3 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -37,9 +37,7 @@
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
-#include "optimizer/clauses.h"
-#include "optimizer/planner.h"
-#include "optimizer/prep.h"
+#include "optimizer/optimizer.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 5d859b7..1831ea8 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -24,8 +24,6 @@
 #include "nodes/extensible.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
-#include "optimizer/clauses.h"
-#include "optimizer/planmain.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/bufmgr.h"
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index ac40168..9a2f1a8 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -54,8 +54,7 @@
 #include "executor/executor.h"
 #include "funcapi.h"
 #include "miscadmin.h"
-#include "optimizer/clauses.h"
-#include "optimizer/var.h"
+#include "optimizer/optimizer.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
 #include "parser/parse_expr.h"
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 5b2b8d2..bd85099 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -42,9 +42,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
-#include "optimizer/clauses.h"
-#include "optimizer/planner.h"
-#include "optimizer/var.h"
+#include "optimizer/optimizer.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ff76499..434be40 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -63,11 +63,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/parsenodes.h"
-#include "optimizer/clauses.h"
-#include "optimizer/planner.h"
-#include "optimizer/predtest.h"
-#include "optimizer/prep.h"
-#include "optimizer/var.h"
+#include "optimizer/optimizer.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 499030c..7b5896b 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -36,8 +36,7 @@
 #include "miscadmin.h"
 #include "nodes/bitmapset.h"
 #include "nodes/makefuncs.h"
-#include "optimizer/clauses.h"
-#include "optimizer/var.h"
+#include "optimizer/optimizer.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_collate.h"
 #include "parser/parse_func.h"
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 35a6485..fa7161e 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -56,7 +56,7 @@
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
-#include "optimizer/var.h"
+#include "optimizer/optimizer.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
 #include "parser/parse_expr.h"
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 2e061a5..e52b806 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -40,8 +40,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
-#include "optimizer/clauses.h"
-#include "optimizer/planner.h"
+#include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 5975c52..9a20460 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -52,7 +52,6 @@
 #include "jit/jit.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
-#include "optimizer/clauses.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
 #include "storage/bufmgr.h"
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index ceea3d6..b79be91 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -39,8 +39,6 @@
 #include "executor/tqueue.h"
 #include "jit/jit.h"
 #include "nodes/nodeFuncs.h"
-#include "optimizer/planmain.h"
-#include "optimizer/planner.h"
 #include "storage/spin.h"
 #include "tcop/tcopprot.h"
 #include "utils/datum.h"
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 2a260a7..263a0f8 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -226,8 +226,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
-#include "optimizer/clauses.h"
-#include "optimizer/tlist.h"
+#include "optimizer/optimizer.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_coerce.h"
 #include "utils/acl.h"
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index 70a4e90..69d5a1f 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -38,7 +38,7 @@
 #include "executor/nodeSubplan.h"
 #include "executor/tqueue.h"
 #include "miscadmin.h"
-#include "optimizer/planmain.h"
+#include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c
index 8635865..4de1d2b 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -23,7 +23,7 @@
 #include "executor/tqueue.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
-#include "optimizer/planmain.h"
+#include "optimizer/optimizer.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"

diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index cdf1ad0..324356e 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -37,7 +37,6 @@
 #include "lib/pairingheap.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
-#include "optimizer/clauses.h"
 #include "utils/array.h"
 #include "utils/datum.h"
 #include "utils/lsyscache.h"
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 731391f..157ac04 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -41,7 +41,7 @@
 #include "executor/nodeWindowAgg.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
-#include "optimizer/clauses.h"
+#include "optimizer/optimizer.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_coerce.h"
 #include "utils/acl.h"
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index 351f6ec..df7e620 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -33,7 +33,6 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
-#include "optimizer/planner.h"
 #include "parser/parse_coerce.h"
 #include "parser/parsetree.h"
 #include "pgstat.h"
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index e1ecfd6..72dcb12 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -35,13 +35,13 @@
 #include "optimizer/cost.h"
 #include "optimizer/geqo.h"
 #include "optimizer/inherit.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/plancat.h"
 #include "optimizer/planner.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
-#include "optimizer/var.h"
 #include "parser/parse_clause.h"
 #include "parser/parsetree.h"
 #include "partitioning/partprune.h"
diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index 5045270..abca03b 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -18,6 +18,7 @@
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/plancat.h"
 #include "utils/fmgroids.h"
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index e288804..6c53f12 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -83,6 +83,7 @@
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/placeholder.h"
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 6e134ae..3454f12 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -24,11 +24,11 @@
 #include "nodes/nodeFuncs.h"
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
 #include "optimizer/restrictinfo.h"
-#include "optimizer/var.h"
 #include "utils/lsyscache.h"


diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index be50d56..7e1a390 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -28,12 +28,11 @@
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
-#include "optimizer/predtest.h"
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
-#include "optimizer/var.h"
 #include "utils/builtins.h"
 #include "utils/bytea.h"
 #include "utils/lsyscache.h"
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index 9dac50e..56d839b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -21,10 +21,9 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/plannodes.h"
-#include "optimizer/clauses.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
-#include "optimizer/tlist.h"
 #include "utils/lsyscache.h"


diff --git a/src/backend/optimizer/path/tidpath.c b/src/backend/optimizer/path/tidpath.c
index 94c88bd..466e996 100644
--- a/src/backend/optimizer/path/tidpath.c
+++ b/src/backend/optimizer/path/tidpath.c
@@ -40,10 +40,10 @@
 #include "catalog/pg_type.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/restrictinfo.h"
-#include "optimizer/var.h"


 /*
diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index 1593dbe..a4efa69 100644
--- a/src/backend/optimizer/plan/analyzejoins.c
+++ b/src/backend/optimizer/plan/analyzejoins.c
@@ -25,11 +25,11 @@
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/joininfo.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
 #include "optimizer/tlist.h"
-#include "optimizer/var.h"
 #include "utils/lsyscache.h"

 /* local functions */
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 97d0c28..5656e10 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -28,16 +28,15 @@
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/paramassign.h"
 #include "optimizer/paths.h"
 #include "optimizer/placeholder.h"
 #include "optimizer/plancat.h"
 #include "optimizer/planmain.h"
-#include "optimizer/predtest.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/subselect.h"
 #include "optimizer/tlist.h"
-#include "optimizer/var.h"
 #include "parser/parse_clause.h"
 #include "parser/parsetree.h"
 #include "partitioning/partprune.h"
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 06ed32a..501bc00 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -21,6 +21,7 @@
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/joininfo.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/placeholder.h"
@@ -28,7 +29,6 @@
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
-#include "optimizer/var.h"
 #include "parser/analyze.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
index d98cb89..8661709 100644
--- a/src/backend/optimizer/plan/planagg.c
+++ b/src/backend/optimizer/plan/planagg.c
@@ -35,6 +35,7 @@
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 3304edb..d5a2fd2 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -43,6 +43,7 @@
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/inherit.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/paramassign.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -52,7 +53,6 @@
 #include "optimizer/prep.h"
 #include "optimizer/subselect.h"
 #include "optimizer/tlist.h"
-#include "optimizer/var.h"
 #include "parser/analyze.h"
 #include "parser/parsetree.h"
 #include "parser/parse_agg.h"
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 6bd3b2d..0213a37 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -19,6 +19,7 @@
 #include "catalog/pg_type.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index da65d04..a998b9b 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -25,13 +25,13 @@
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/paramassign.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
 #include "optimizer/subselect.h"
-#include "optimizer/var.h"
 #include "parser/parse_relation.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/builtins.h"
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 1f993d4..bfcdc7c 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -27,11 +27,11 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/placeholder.h"
 #include "optimizer/prep.h"
 #include "optimizer/subselect.h"
 #include "optimizer/tlist.h"
-#include "optimizer/var.h"
 #include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
diff --git a/src/backend/optimizer/prep/prepqual.c b/src/backend/optimizer/prep/prepqual.c
index 2bd6c20..e9a9497 100644
--- a/src/backend/optimizer/prep/prepqual.c
+++ b/src/backend/optimizer/prep/prepqual.c
@@ -33,7 +33,7 @@

 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
-#include "optimizer/clauses.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/prep.h"
 #include "utils/lsyscache.h"

diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 0e045f1..5392d1a 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -44,9 +44,9 @@
 #include "access/table.h"
 #include "catalog/pg_type.h"
 #include "nodes/makefuncs.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/prep.h"
 #include "optimizer/tlist.h"
-#include "optimizer/var.h"
 #include "parser/parsetree.h"
 #include "parser/parse_coerce.h"
 #include "rewrite/rewriteHandler.h"
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 816aa86..841a257 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -34,9 +34,9 @@
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/planmain.h"
 #include "optimizer/prep.h"
-#include "optimizer/var.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_coerce.h"
diff --git a/src/backend/optimizer/util/orclauses.c b/src/backend/optimizer/util/orclauses.c
index c62ba88..b671581 100644
--- a/src/backend/optimizer/util/orclauses.c
+++ b/src/backend/optimizer/util/orclauses.c
@@ -19,6 +19,7 @@
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/orclauses.h"
 #include "optimizer/restrictinfo.h"

diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index b2637d0..257f0bf 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -23,13 +23,13 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
-#include "optimizer/var.h"
 #include "parser/parsetree.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
diff --git a/src/backend/optimizer/util/placeholder.c b/src/backend/optimizer/util/placeholder.c
index b24478e..798aa8c 100644
--- a/src/backend/optimizer/util/placeholder.c
+++ b/src/backend/optimizer/util/placeholder.c
@@ -17,10 +17,10 @@

 #include "nodes/nodeFuncs.h"
 #include "optimizer/cost.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/placeholder.h"
 #include "optimizer/planmain.h"
-#include "optimizer/var.h"
 #include "utils/lsyscache.h"

 /* Local functions */
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 261492e..cb7f6f9 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -35,8 +35,8 @@
 #include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/plancat.h"
-#include "optimizer/predtest.h"
 #include "optimizer/prep.h"
 #include "partitioning/partbounds.h"
 #include "parser/parse_relation.h"
diff --git a/src/backend/optimizer/util/predtest.c b/src/backend/optimizer/util/predtest.c
index ecbb0db..3b260b1 100644
--- a/src/backend/optimizer/util/predtest.c
+++ b/src/backend/optimizer/util/predtest.c
@@ -21,8 +21,8 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
-#include "optimizer/clauses.h"
-#include "optimizer/predtest.h"
+#include "nodes/relation.h"
+#include "optimizer/optimizer.h"
 #include "utils/array.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c
index c1bda81..1c47c70 100644
--- a/src/backend/optimizer/util/restrictinfo.c
+++ b/src/backend/optimizer/util/restrictinfo.c
@@ -17,8 +17,8 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/restrictinfo.h"
-#include "optimizer/var.h"


 static RestrictInfo *make_restrictinfo_internal(Expr *clause,
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
index d0cc14f..14d1c67 100644
--- a/src/backend/optimizer/util/tlist.c
+++ b/src/backend/optimizer/util/tlist.c
@@ -17,6 +17,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/cost.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/tlist.h"


diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index 5116d7f..315c81e 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -22,8 +22,8 @@

 #include "access/sysattr.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/prep.h"
-#include "optimizer/var.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"

diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 8f96558..f8dd8ac 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -29,7 +29,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
-#include "optimizer/var.h"
+#include "optimizer/optimizer.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_clause.h"
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 5b73378..183ea0f 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -19,8 +19,7 @@
 #include "catalog/pg_type.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
-#include "optimizer/tlist.h"
-#include "optimizer/var.h"
+#include "optimizer/optimizer.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 8805543..c6ce101 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -31,8 +31,7 @@
 #include "commands/defrem.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
-#include "optimizer/tlist.h"
-#include "optimizer/var.h"
+#include "optimizer/optimizer.h"
 #include "parser/analyze.h"
 #include "parser/parsetree.h"
 #include "parser/parser.h"
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 3e0a762..7fc8d63 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -20,8 +20,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
-#include "optimizer/tlist.h"
-#include "optimizer/var.h"
+#include "optimizer/optimizer.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 445e81a..a37d1f1 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -50,9 +50,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
-#include "optimizer/clauses.h"
-#include "optimizer/planner.h"
-#include "optimizer/var.h"
+#include "optimizer/optimizer.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
index f21c9b3..d478ae7 100644
--- a/src/backend/partitioning/partbounds.c
+++ b/src/backend/partitioning/partbounds.c
@@ -22,7 +22,6 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
-#include "optimizer/clauses.h"
 #include "parser/parse_coerce.h"
 #include "partitioning/partprune.h"
 #include "partitioning/partbounds.h"
diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c
index f5155fe..8c97219 100644
--- a/src/backend/partitioning/partprune.c
+++ b/src/backend/partitioning/partprune.c
@@ -45,12 +45,8 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/appendinfo.h"
-#include "optimizer/clauses.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
-#include "optimizer/planner.h"
-#include "optimizer/predtest.h"
-#include "optimizer/prep.h"
-#include "optimizer/var.h"
 #include "partitioning/partprune.h"
 #include "partitioning/partbounds.h"
 #include "rewrite/rewriteManip.h"
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 2c49c71..f951651 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -23,61 +23,46 @@

 #include "postgres.h"

-#include "miscadmin.h"
-#include "pgstat.h"
-#include "funcapi.h"
-
 #include "access/table.h"
 #include "access/xact.h"
 #include "access/xlog_internal.h"
-
 #include "catalog/catalog.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_subscription.h"
 #include "catalog/pg_subscription_rel.h"
-
 #include "commands/tablecmds.h"
 #include "commands/trigger.h"
-
 #include "executor/executor.h"
 #include "executor/nodeModifyTable.h"
-
+#include "funcapi.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqsignal.h"
-
 #include "mb/pg_wchar.h"
-
+#include "miscadmin.h"
 #include "nodes/makefuncs.h"
-
-#include "optimizer/planner.h"
-
+#include "optimizer/optimizer.h"
 #include "parser/parse_relation.h"
-
+#include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/postmaster.h"
 #include "postmaster/walwriter.h"
-
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/logicalproto.h"
 #include "replication/logicalrelation.h"
 #include "replication/logicalworker.h"
-#include "replication/reorderbuffer.h"
 #include "replication/origin.h"
+#include "replication/reorderbuffer.h"
 #include "replication/snapbuild.h"
 #include "replication/walreceiver.h"
 #include "replication/worker_internal.h"
-
 #include "rewrite/rewriteHandler.h"
-
 #include "storage/bufmgr.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
-
 #include "tcop/tcopprot.h"
-
 #include "utils/builtins.h"
 #include "utils/catcache.h"
 #include "utils/datum.h"
@@ -87,8 +72,8 @@
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
-#include "utils/timeout.h"
 #include "utils/syscache.h"
+#include "utils/timeout.h"

 #define NAPTIME_PER_CYCLE 1000    /* max sleep time between cycles (1s) */

diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index fd7a751..57aee3d 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -17,7 +17,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/plannodes.h"
-#include "optimizer/clauses.h"
+#include "nodes/relation.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_relation.h"
 #include "parser/parsetree.h"
diff --git a/src/backend/statistics/dependencies.c b/src/backend/statistics/dependencies.c
index a8e9f72..13492ce 100644
--- a/src/backend/statistics/dependencies.c
+++ b/src/backend/statistics/dependencies.c
@@ -20,8 +20,7 @@
 #include "lib/stringinfo.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
-#include "optimizer/cost.h"
-#include "optimizer/var.h"
+#include "optimizer/optimizer.h"
 #include "nodes/nodes.h"
 #include "nodes/relation.h"
 #include "statistics/extended_stats_internal.h"
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index e773f20..36cfd50 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -49,7 +49,7 @@
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "nodes/print.h"
-#include "optimizer/planner.h"
+#include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "pg_trace.h"
 #include "parser/analyze.h"
diff --git a/src/backend/utils/adt/array_selfuncs.c b/src/backend/utils/adt/array_selfuncs.c
index ad1201a..870131b 100644
--- a/src/backend/utils/adt/array_selfuncs.c
+++ b/src/backend/utils/adt/array_selfuncs.c
@@ -20,7 +20,6 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
-#include "optimizer/clauses.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
diff --git a/src/backend/utils/adt/orderedsetaggs.c b/src/backend/utils/adt/orderedsetaggs.c
index 6075bc3..2d384a9 100644
--- a/src/backend/utils/adt/orderedsetaggs.c
+++ b/src/backend/utils/adt/orderedsetaggs.c
@@ -22,7 +22,7 @@
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
-#include "optimizer/tlist.h"
+#include "optimizer/optimizer.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 302df16..09bc7e6 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -49,7 +49,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
-#include "optimizer/tlist.h"
+#include "optimizer/optimizer.h"
 #include "parser/parse_node.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_func.h"
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 4af8c80..fb00504 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -121,12 +121,11 @@
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/plancat.h"
-#include "optimizer/predtest.h"
 #include "optimizer/restrictinfo.h"
-#include "optimizer/var.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
 #include "parser/parsetree.h"
diff --git a/src/backend/utils/cache/partcache.c b/src/backend/utils/cache/partcache.c
index 2404073..1b50f28 100644
--- a/src/backend/utils/cache/partcache.c
+++ b/src/backend/utils/cache/partcache.c
@@ -25,8 +25,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
-#include "optimizer/clauses.h"
-#include "optimizer/planner.h"
+#include "optimizer/optimizer.h"
 #include "partitioning/partbounds.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 3f46b5d..9851bd4 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -61,10 +61,7 @@
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
-#include "optimizer/cost.h"
-#include "optimizer/planmain.h"
-#include "optimizer/planner.h"
-#include "optimizer/prep.h"
+#include "optimizer/optimizer.h"
 #include "parser/analyze.h"
 #include "parser/parsetree.h"
 #include "storage/lmgr.h"
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 12bba1b..af96a03 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -71,9 +71,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
-#include "optimizer/clauses.h"
-#include "optimizer/prep.h"
-#include "optimizer/var.h"
+#include "optimizer/optimizer.h"
 #include "partitioning/partbounds.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rowsecurity.h"
diff --git a/src/backend/utils/cache/spccache.c b/src/backend/utils/cache/spccache.c
index 1d0c16b..6309a01 100644
--- a/src/backend/utils/cache/spccache.c
+++ b/src/backend/utils/cache/spccache.c
@@ -22,7 +22,7 @@
 #include "catalog/pg_tablespace.h"
 #include "commands/tablespace.h"
 #include "miscadmin.h"
-#include "optimizer/cost.h"
+#include "optimizer/optimizer.h"
 #include "storage/bufmgr.h"
 #include "utils/catcache.h"
 #include "utils/hsearch.h"
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 2fb7dd3..7857424 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -59,7 +59,7 @@
 #include "commands/defrem.h"
 #include "executor/executor.h"
 #include "lib/dshash.h"
-#include "optimizer/planner.h"
+#include "optimizer/optimizer.h"
 #include "storage/lwlock.h"
 #include "utils/builtins.h"
 #include "utils/catcache.h"
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index c216ed0..98d75be 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -50,6 +50,7 @@
 #include "miscadmin.h"
 #include "optimizer/cost.h"
 #include "optimizer/geqo.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
 #include "parser/parse_expr.h"
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 3430061..366b0a6 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -159,11 +159,17 @@ typedef struct PlannerGlobal
  * It holds links to all of the planner's working state, in addition to the
  * original Query.  Note that at present the planner extensively modifies
  * the passed-in Query data structure; someday that should stop.
+ *
+ * For reasons explained in optimizer/optimizer.h, we define the typedef
+ * either here or in that header, whichever is read first.
  *----------
  */
-struct AppendRelInfo;
+#ifndef HAVE_PLANNERINFO_TYPEDEF
+typedef struct PlannerInfo PlannerInfo;
+#define HAVE_PLANNERINFO_TYPEDEF 1
+#endif

-typedef struct PlannerInfo
+struct PlannerInfo
 {
     NodeTag        type;

@@ -173,7 +179,7 @@ typedef struct PlannerInfo

     Index        query_level;    /* 1 at the outermost Query */

-    struct PlannerInfo *parent_root;    /* NULL at outermost Query */
+    PlannerInfo *parent_root;    /* NULL at outermost Query */

     /*
      * plan_params contains the expressions that this query level needs to
@@ -343,7 +349,7 @@ typedef struct PlannerInfo

     /* Does this query modify any partition key columns? */
     bool        partColsUpdated;
-} PlannerInfo;
+};


 /*
@@ -2066,8 +2072,12 @@ typedef struct PlaceHolderVar
  * plain innerjoin semantics.  Note that lhs_strict, delay_upper_joins, and
  * of course the semi_xxx fields are not set meaningfully within such structs.
  */
+#ifndef HAVE_SPECIALJOININFO_TYPEDEF
+typedef struct SpecialJoinInfo SpecialJoinInfo;
+#define HAVE_SPECIALJOININFO_TYPEDEF 1
+#endif

-typedef struct SpecialJoinInfo
+struct SpecialJoinInfo
 {
     NodeTag        type;
     Relids        min_lefthand;    /* base relids in minimum LHS for join */
@@ -2082,7 +2092,7 @@ typedef struct SpecialJoinInfo
     bool        semi_can_hash;    /* true if semi_operators are all hash */
     List       *semi_operators; /* OIDs of equality join operators */
     List       *semi_rhs_exprs; /* righthand-side expressions of these ops */
-} SpecialJoinInfo;
+};

 /*
  * Append-relation info.
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 3f53428..cfedd42 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -35,9 +35,6 @@ extern double expression_returns_set_rows(Node *clause);

 extern bool contain_subplans(Node *clause);

-extern bool contain_mutable_functions(Node *clause);
-extern bool contain_volatile_functions(Node *clause);
-extern bool contain_volatile_functions_not_nextval(Node *clause);
 extern char max_parallel_hazard(Query *parse);
 extern bool is_parallel_safe(PlannerInfo *root, Node *node);
 extern bool contain_nonstrict_functions(Node *clause);
@@ -56,17 +53,7 @@ extern int    NumRelids(Node *clause);
 extern void CommuteOpExpr(OpExpr *clause);
 extern void CommuteRowCompareExpr(RowCompareExpr *clause);

-extern Node *eval_const_expressions(PlannerInfo *root, Node *node);
-
-extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
-
-extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
-                           Oid result_collation);
-
 extern Query *inline_set_returning_function(PlannerInfo *root,
                               RangeTblEntry *rte);

-extern List *expand_function_arguments(List *args, Oid result_type,
-                          HeapTuple func_tuple);
-
 #endif                            /* CLAUSES_H */
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index e7005b4..d5912e3 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -44,15 +44,7 @@ typedef enum
  *      routines to compute costs and sizes
  */

-/* parameter variables and flags */
-extern PGDLLIMPORT double seq_page_cost;
-extern PGDLLIMPORT double random_page_cost;
-extern PGDLLIMPORT double cpu_tuple_cost;
-extern PGDLLIMPORT double cpu_index_tuple_cost;
-extern PGDLLIMPORT double cpu_operator_cost;
-extern PGDLLIMPORT double parallel_tuple_cost;
-extern PGDLLIMPORT double parallel_setup_cost;
-extern PGDLLIMPORT int effective_cache_size;
+/* parameter variables and flags (see also optimizer.h) */
 extern PGDLLIMPORT Cost disable_cost;
 extern PGDLLIMPORT int max_parallel_workers_per_gather;
 extern PGDLLIMPORT bool enable_seqscan;
@@ -74,7 +66,6 @@ extern PGDLLIMPORT bool enable_parallel_hash;
 extern PGDLLIMPORT bool enable_partition_pruning;
 extern PGDLLIMPORT int constraint_exclusion;

-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,
@@ -163,6 +154,10 @@ extern void final_cost_hashjoin(PlannerInfo *root, HashPath *path,
                     JoinPathExtraData *extra);
 extern void cost_gather(GatherPath *path, PlannerInfo *root,
             RelOptInfo *baserel, ParamPathInfo *param_info, double *rows);
+extern void cost_gather_merge(GatherMergePath *path, PlannerInfo *root,
+                  RelOptInfo *rel, ParamPathInfo *param_info,
+                  Cost input_startup_cost, Cost input_total_cost,
+                  double *rows);
 extern void cost_subplan(PlannerInfo *root, SubPlan *subplan, Plan *plan);
 extern void cost_qual_eval(QualCost *cost, List *quals, PlannerInfo *root);
 extern void cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root);
@@ -201,23 +196,4 @@ extern PathTarget *set_pathtarget_cost_width(PlannerInfo *root, PathTarget *targ
 extern double compute_bitmap_pages(PlannerInfo *root, RelOptInfo *baserel,
                      Path *bitmapqual, int loop_count, Cost *cost, double *tuple);

-/*
- * prototypes for clausesel.c
- *      routines to compute clause selectivities
- */
-extern Selectivity clauselist_selectivity(PlannerInfo *root,
-                       List *clauses,
-                       int varRelid,
-                       JoinType jointype,
-                       SpecialJoinInfo *sjinfo);
-extern Selectivity clause_selectivity(PlannerInfo *root,
-                   Node *clause,
-                   int varRelid,
-                   JoinType jointype,
-                   SpecialJoinInfo *sjinfo);
-extern void cost_gather_merge(GatherMergePath *path, PlannerInfo *root,
-                  RelOptInfo *rel, ParamPathInfo *param_info,
-                  Cost input_startup_cost, Cost input_total_cost,
-                  double *rows);
-
 #endif                            /* COST_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index e69de29..1cd5c80 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -0,0 +1,175 @@
+/*-------------------------------------------------------------------------
+ *
+ * optimizer.h
+ *      External API for the Postgres planner.
+ *
+ * This header is meant to define everything that the core planner
+ * exposes for use by non-planner modules.
+ *
+ * Note that there are files outside src/backend/optimizer/ that are
+ * considered planner modules, because they're too much in bed with
+ * planner operations to be treated otherwise.  FDW planning code is an
+ * example.  For the most part, however, code outside the core planner
+ * should not need to include any optimizer/ header except this one.
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/optimizer/optimizer.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef OPTIMIZER_H
+#define OPTIMIZER_H
+
+#include "nodes/parsenodes.h"
+
+/*
+ * We don't want to include nodes/relation.h here, because non-planner
+ * code should generally treat PlannerInfo as an opaque typedef.
+ * But we'd like such code to use that typedef name, so define the
+ * typedef either here or in relation.h, whichever is read first.
+ */
+#ifndef HAVE_PLANNERINFO_TYPEDEF
+typedef struct PlannerInfo PlannerInfo;
+#define HAVE_PLANNERINFO_TYPEDEF 1
+#endif
+
+/* Likewise for SpecialJoinInfo. */
+#ifndef HAVE_SPECIALJOININFO_TYPEDEF
+typedef struct SpecialJoinInfo SpecialJoinInfo;
+#define HAVE_SPECIALJOININFO_TYPEDEF 1
+#endif
+
+/* It also seems best not to include plannodes.h, params.h, or htup.h here */
+struct PlannedStmt;
+struct ParamListInfoData;
+struct HeapTupleData;
+
+
+/* in path/clausesel.c: */
+
+extern Selectivity clause_selectivity(PlannerInfo *root,
+                   Node *clause,
+                   int varRelid,
+                   JoinType jointype,
+                   SpecialJoinInfo *sjinfo);
+extern Selectivity clauselist_selectivity(PlannerInfo *root,
+                       List *clauses,
+                       int varRelid,
+                       JoinType jointype,
+                       SpecialJoinInfo *sjinfo);
+
+/* in path/costsize.c: */
+
+/* widely used cost parameters */
+extern PGDLLIMPORT double seq_page_cost;
+extern PGDLLIMPORT double random_page_cost;
+extern PGDLLIMPORT double cpu_tuple_cost;
+extern PGDLLIMPORT double cpu_index_tuple_cost;
+extern PGDLLIMPORT double cpu_operator_cost;
+extern PGDLLIMPORT double parallel_tuple_cost;
+extern PGDLLIMPORT double parallel_setup_cost;
+extern PGDLLIMPORT int effective_cache_size;
+
+extern double clamp_row_est(double nrows);
+
+/* in plan/planner.c: */
+
+/* possible values for force_parallel_mode */
+typedef enum
+{
+    FORCE_PARALLEL_OFF,
+    FORCE_PARALLEL_ON,
+    FORCE_PARALLEL_REGRESS
+}            ForceParallelMode;
+
+/* GUC parameters */
+extern int    force_parallel_mode;
+extern bool parallel_leader_participation;
+
+extern struct PlannedStmt *planner(Query *parse, int cursorOptions,
+        struct ParamListInfoData *boundParams);
+
+extern Expr *expression_planner(Expr *expr);
+extern Expr *expression_planner_with_deps(Expr *expr,
+                             List **relationOids,
+                             List **invalItems);
+
+extern bool plan_cluster_use_sort(Oid tableOid, Oid indexOid);
+extern int    plan_create_index_workers(Oid tableOid, Oid indexOid);
+
+/* in plan/setrefs.c: */
+
+extern void extract_query_dependencies(Node *query,
+                           List **relationOids,
+                           List **invalItems,
+                           bool *hasRowSecurity);
+
+/* in prep/prepqual.c: */
+
+extern Node *negate_clause(Node *node);
+extern Expr *canonicalize_qual(Expr *qual, bool is_check);
+
+/* in util/clauses.c: */
+
+extern bool contain_mutable_functions(Node *clause);
+extern bool contain_volatile_functions(Node *clause);
+extern bool contain_volatile_functions_not_nextval(Node *clause);
+
+extern Node *eval_const_expressions(PlannerInfo *root, Node *node);
+
+extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
+
+extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
+              Oid result_collation);
+
+extern List *expand_function_arguments(List *args, Oid result_type,
+                          struct HeapTupleData *func_tuple);
+
+/* in util/predtest.c: */
+
+extern bool predicate_implied_by(List *predicate_list, List *clause_list,
+                     bool weak);
+extern bool predicate_refuted_by(List *predicate_list, List *clause_list,
+                     bool weak);
+
+/* in util/tlist.c: */
+
+extern int    count_nonjunk_tlist_entries(List *tlist);
+extern TargetEntry *get_sortgroupref_tle(Index sortref,
+                     List *targetList);
+extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
+                        List *targetList);
+extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
+                         List *targetList);
+extern List *get_sortgrouplist_exprs(List *sgClauses,
+                        List *targetList);
+extern SortGroupClause *get_sortgroupref_clause(Index sortref,
+                        List *clauses);
+extern SortGroupClause *get_sortgroupref_clause_noerr(Index sortref,
+                              List *clauses);
+
+/* in util/var.c: */
+
+/* Bits that can be OR'd into the flags argument of pull_var_clause() */
+#define PVC_INCLUDE_AGGREGATES    0x0001    /* include Aggrefs in output list */
+#define PVC_RECURSE_AGGREGATES    0x0002    /* recurse into Aggref arguments */
+#define PVC_INCLUDE_WINDOWFUNCS 0x0004    /* include WindowFuncs in output list */
+#define PVC_RECURSE_WINDOWFUNCS 0x0008    /* recurse into WindowFunc arguments */
+#define PVC_INCLUDE_PLACEHOLDERS    0x0010    /* include PlaceHolderVars in
+                                             * output list */
+#define PVC_RECURSE_PLACEHOLDERS    0x0020    /* recurse into PlaceHolderVar
+                                             * arguments */
+
+extern Bitmapset *pull_varnos(Node *node);
+extern Bitmapset *pull_varnos_of_level(Node *node, int levelsup);
+extern void pull_varattnos(Node *node, Index varno, Bitmapset **varattnos);
+extern List *pull_vars_of_level(Node *node, int levelsup);
+extern bool contain_var_clause(Node *node);
+extern bool contain_vars_of_level(Node *node, int levelsup);
+extern int    locate_var_of_level(Node *node, int levelsup);
+extern List *pull_var_clause(Node *node, int flags);
+extern Node *flatten_join_alias_vars(Query *query, Node *node);
+
+#endif                            /* OPTIMIZER_H */
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index bec0c38..b2e1c07 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -17,19 +17,9 @@
 #include "nodes/plannodes.h"
 #include "nodes/relation.h"

-/* possible values for force_parallel_mode */
-typedef enum
-{
-    FORCE_PARALLEL_OFF,
-    FORCE_PARALLEL_ON,
-    FORCE_PARALLEL_REGRESS
-}            ForceParallelMode;
-
 /* GUC parameters */
 #define DEFAULT_CURSOR_TUPLE_FRACTION 0.1
 extern double cursor_tuple_fraction;
-extern int    force_parallel_mode;
-extern bool parallel_leader_participation;

 /* query_planner callback to compute query_pathkeys */
 typedef void (*query_pathkeys_callback) (PlannerInfo *root, void *extra);
@@ -119,10 +109,6 @@ extern bool innerrel_is_unique(PlannerInfo *root,
 extern Plan *set_plan_references(PlannerInfo *root, Plan *plan);
 extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid);
 extern void record_plan_type_dependency(PlannerInfo *root, Oid typid);
-extern void extract_query_dependencies(Node *query,
-                           List **relationOids,
-                           List **invalItems,
-                           bool *hasRowSecurity);
 extern bool extract_query_dependencies_walker(Node *node, PlannerInfo *root);

 #endif                            /* PLANMAIN_H */
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index 8616681..769a2f8 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -3,6 +3,10 @@
  * planner.h
  *      prototypes for planner.c.
  *
+ * Note that the primary entry points for planner.c are declared in
+ * optimizer/optimizer.h, because they're intended to be called from
+ * non-planner code.  Declarations here are meant for use by other
+ * planner modules.
  *
  * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -33,8 +37,6 @@ typedef void (*create_upper_paths_hook_type) (PlannerInfo *root,
 extern PGDLLIMPORT create_upper_paths_hook_type create_upper_paths_hook;


-extern PlannedStmt *planner(Query *parse, int cursorOptions,
-        ParamListInfo boundParams);
 extern PlannedStmt *standard_planner(Query *parse, int cursorOptions,
                  ParamListInfo boundParams);

@@ -54,14 +56,6 @@ extern void mark_partial_aggref(Aggref *agg, AggSplit aggsplit);
 extern Path *get_cheapest_fractional_path(RelOptInfo *rel,
                              double tuple_fraction);

-extern Expr *expression_planner(Expr *expr);
-extern Expr *expression_planner_with_deps(Expr *expr,
-                             List **relationOids,
-                             List **invalItems);
-
 extern Expr *preprocess_phv_expression(PlannerInfo *root, Expr *expr);

-extern bool plan_cluster_use_sort(Oid tableOid, Oid indexOid);
-extern int    plan_create_index_workers(Oid tableOid, Oid indexOid);
-
 #endif                            /* PLANNER_H */
diff --git a/src/include/optimizer/predtest.h b/src/include/optimizer/predtest.h
index d5abe79..e69de29 100644
--- a/src/include/optimizer/predtest.h
+++ b/src/include/optimizer/predtest.h
@@ -1,25 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * predtest.h
- *      prototypes for predtest.c
- *
- *
- * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * src/include/optimizer/predtest.h
- *
- *-------------------------------------------------------------------------
- */
-#ifndef PREDTEST_H
-#define PREDTEST_H
-
-#include "nodes/primnodes.h"
-
-
-extern bool predicate_implied_by(List *predicate_list, List *clause_list,
-                     bool weak);
-extern bool predicate_refuted_by(List *predicate_list, List *clause_list,
-                     bool weak);
-
-#endif                            /* PREDTEST_H */
diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h
index 3ad8dcb..9a7a584 100644
--- a/src/include/optimizer/prep.h
+++ b/src/include/optimizer/prep.h
@@ -30,12 +30,6 @@ extern Relids get_relids_in_jointree(Node *jtnode, bool include_joins);
 extern Relids get_relids_for_join(Query *query, int joinrelid);

 /*
- * prototypes for prepqual.c
- */
-extern Node *negate_clause(Node *node);
-extern Expr *canonicalize_qual(Expr *qual, bool is_check);
-
-/*
  * prototypes for preptlist.c
  */
 extern List *preprocess_targetlist(PlannerInfo *root);
diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h
index 8b967f9..13ac7b8 100644
--- a/src/include/optimizer/tlist.h
+++ b/src/include/optimizer/tlist.h
@@ -24,8 +24,6 @@ extern List *add_to_flat_tlist(List *tlist, List *exprs);

 extern List *get_tlist_exprs(List *tlist, bool includeJunk);

-extern int    count_nonjunk_tlist_entries(List *tlist);
-
 extern bool tlist_same_exprs(List *tlist1, List *tlist2);

 extern bool tlist_same_datatypes(List *tlist, List *colTypes, bool junkOK);
@@ -33,20 +31,6 @@ extern bool tlist_same_collations(List *tlist, List *colCollations, bool junkOK)

 extern void apply_tlist_labeling(List *dest_tlist, List *src_tlist);

-extern TargetEntry *get_sortgroupref_tle(Index sortref,
-                     List *targetList);
-extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
-                        List *targetList);
-extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
-                         List *targetList);
-extern List *get_sortgrouplist_exprs(List *sgClauses,
-                        List *targetList);
-
-extern SortGroupClause *get_sortgroupref_clause(Index sortref,
-                        List *clauses);
-extern SortGroupClause *get_sortgroupref_clause_noerr(Index sortref,
-                              List *clauses);
-
 extern Oid *extract_grouping_ops(List *groupClause);
 extern AttrNumber *extract_grouping_cols(List *groupClause, List *tlist);
 extern bool grouping_is_sortable(List *groupClause);
diff --git a/src/include/optimizer/var.h b/src/include/optimizer/var.h
index f975074..e69de29 100644
--- a/src/include/optimizer/var.h
+++ b/src/include/optimizer/var.h
@@ -1,40 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * var.h
- *      prototypes for optimizer/util/var.c.
- *
- *
- * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * src/include/optimizer/var.h
- *
- *-------------------------------------------------------------------------
- */
-#ifndef VAR_H
-#define VAR_H
-
-#include "nodes/relation.h"
-
-/* Bits that can be OR'd into the flags argument of pull_var_clause() */
-#define PVC_INCLUDE_AGGREGATES    0x0001    /* include Aggrefs in output list */
-#define PVC_RECURSE_AGGREGATES    0x0002    /* recurse into Aggref arguments */
-#define PVC_INCLUDE_WINDOWFUNCS 0x0004    /* include WindowFuncs in output list */
-#define PVC_RECURSE_WINDOWFUNCS 0x0008    /* recurse into WindowFunc arguments */
-#define PVC_INCLUDE_PLACEHOLDERS    0x0010    /* include PlaceHolderVars in
-                                             * output list */
-#define PVC_RECURSE_PLACEHOLDERS    0x0020    /* recurse into PlaceHolderVar
-                                             * arguments */
-
-
-extern Relids pull_varnos(Node *node);
-extern Relids pull_varnos_of_level(Node *node, int levelsup);
-extern void pull_varattnos(Node *node, Index varno, Bitmapset **varattnos);
-extern List *pull_vars_of_level(Node *node, int levelsup);
-extern bool contain_var_clause(Node *node);
-extern bool contain_vars_of_level(Node *node, int levelsup);
-extern int    locate_var_of_level(Node *node, int levelsup);
-extern List *pull_var_clause(Node *node, int flags);
-extern Node *flatten_join_alias_vars(Query *query, Node *node);
-
-#endif                            /* VAR_H */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 45f10f9..85aa613 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -30,8 +30,7 @@
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
-#include "optimizer/clauses.h"
-#include "optimizer/planner.h"
+#include "optimizer/optimizer.h"
 #include "parser/parse_coerce.h"
 #include "parser/scansup.h"
 #include "storage/proc.h"
diff --git a/src/test/modules/test_predtest/test_predtest.c b/src/test/modules/test_predtest/test_predtest.c
index c03748c..10cd4f9 100644
--- a/src/test/modules/test_predtest/test_predtest.c
+++ b/src/test/modules/test_predtest/test_predtest.c
@@ -18,8 +18,7 @@
 #include "executor/spi.h"
 #include "funcapi.h"
 #include "nodes/makefuncs.h"
-#include "optimizer/clauses.h"
-#include "optimizer/predtest.h"
+#include "optimizer/optimizer.h"
 #include "utils/builtins.h"

 PG_MODULE_MAGIC;

Re: Proposed refactoring of planner header files

От
Andres Freund
Дата:
Hi,

On 2019-01-28 15:17:19 -0500, Tom Lane wrote:
> In <6044.1548524131@sss.pgh.pa.us> I worried about how much planner
> stuff that patch might end up dragging into files that contain
> planner support functions, and suggested that we could refactor the
> planner's header files to reduce the inclusion footprint.  Attached
> are some proposed patches to improve the situation.  The work isn't
> fully done yet, but I was hoping to get buy-in on this approach
> before going further.

> The basic idea here is to create a new header file, which I chose
> to call optimizer/optimizer.h, and put into it just the stuff that
> "outside" callers of the planner might need.  For this purpose
> "outside" can be approximated as "doesn't really need to know what
> is in relation.h", ie Paths and related data structures.  I expect
> that planner support functions will mostly be working with parsetree
> data structures for their functions, so they should fit that
> restriction.  In some cases they need to be able to pass a PlannerInfo
> pointer through to some planner function they want to call, but they
> can treat the pointer as an opaque type.  This worked out pretty well,
> as I was able to eliminate uses of all other optimizer/ headers (and,
> therefore, relation.h) from all but four or five files outside
> backend/optimizer/.  The holdouts are mostly places that are pretty
> much in bed with the planner anyway, such as statistics/dependencies.c.


Without having studied the patch in any detail, that seems a worthwhile
goal to me.


> Since I was intentionally trying to limit what optimizer.h pulls in,
> and in particular not let it include relation.h, I needed an opaque
> typedef for PlannerInfo.  On the other hand relation.h also needs to
> typedef that.  I fixed that with a method that we've not used in our
> code AFAIK, but is really common in system headers: there's a #define
> symbol to remember whether we've created the typedef, and including
> both headers in either order will work fine.

Ugh, isn't it nicer to just use the underlying struct type instead of
that?  Or alternatively we could expand our compiler demands to require
that redundant typedefs are allowed - I'm not sure there's any animal
left that doesn't support that (rather than triggering an error it via
an intentionally set flag).


> I'm really unhappy that force_parallel_mode and
> parallel_leader_participation are being treated as planner GUCs.
> They are not that, IMO, because they also affect the behavior of
> the executor, cf HandleParallelMessage, ExecGather, ExecGatherMerge.
> This is somewhere between ill-considered and outright broken: what
> happens if the values change between planning and execution?  I think
> we probably need to fix things so that those variables do not need to
> be looked at by the executor, carrying them forward in the plan
> tree if necessary.  Then they'd not need to be exposed by
> optimizer.h, and indeed I think the mentioned modules wouldn't
> need any optimizer inclusions anymore.

Stylistically I agree with that (and it's what I ended up doing for JIT
compilation as well), but do you see concrete harm here? I don't think
it's super likely that anybody changes these on the fly and expects
immediate effects?


> I would have exposed estimate_rel_size, which is needed by
> access/hash/hash.c, except that it requires Relation and
> BlockNumber typedefs.  The incremental value from keeping
> hash.c from using plancat.h probably isn't worth widening
> optimizer.h's #include footprint further.  Also, I wonder
> whether that whole area needs a rethink for pluggable storage.

The pluggable storage patchset currently makes estimate_rel_size() a
callback for table-like objects. While using BlockNumber isn't all that
pretty (it's one of a few tableam functions dealing with blocks, the
others being samplescan and bitmapscan). I've been wondering whether we
should change the API to return the size in a non-block oriented way,
but given the current costing I wasn't sure that's worth much.

There's an XXX in the changed code wondering whether we should make
estimate_rel_size() also call a callback for indexes, that'd e.g. allow
better per-index logic (c.f. the function's comment about gist).

What kind of rejiggering were you thinking of re pluggable storage?

Greetings,

Andres Freund


Re: Proposed refactoring of planner header files

От
Robert Haas
Дата:
On Mon, Jan 28, 2019 at 3:17 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> I'm really unhappy that force_parallel_mode and
> parallel_leader_participation are being treated as planner GUCs.
> They are not that, IMO, because they also affect the behavior of
> the executor, cf HandleParallelMessage, ExecGather, ExecGatherMerge.
> This is somewhere between ill-considered and outright broken: what
> happens if the values change between planning and execution?

The only use of parallel_leader_participation at plan time seems to be
to twiddle the costing, and the use of it in the executor is to decide
whether to have the leader participate.  So if the values differ,
you'll get a plan running a behavior for which plan selection was not
optimized.  I don't know whether it's useful to intentionally allow
this so that you can see how the same plan behaves under the other
setting, or whether it's just a wart we'd be better off without.  It
might be confusing, though, if you change the setting and it doesn't
force a replan.

Similarly, the use of force_parallel_mode in HandleParallelMessage()
really just affects what CONTEXT lines you get.  That may be
ill-considered but it doesn't seem outright broken.  Here again, I'm
not really sure that forcibly preserving the plan-time value at
execution time would really result in happier users.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


Re: Proposed refactoring of planner header files

От
Tom Lane
Дата:
Andres Freund <andres@anarazel.de> writes:
> On 2019-01-28 15:17:19 -0500, Tom Lane wrote:
>> Since I was intentionally trying to limit what optimizer.h pulls in,
>> and in particular not let it include relation.h, I needed an opaque
>> typedef for PlannerInfo.  On the other hand relation.h also needs to
>> typedef that.  I fixed that with a method that we've not used in our
>> code AFAIK, but is really common in system headers: there's a #define
>> symbol to remember whether we've created the typedef, and including
>> both headers in either order will work fine.

> Ugh, isn't it nicer to just use the underlying struct type instead of
> that?

No, because that'd mean that anyplace relying on optimizer.h would also
have to write "struct PlannerInfo" rather than "PlannerInfo"; the
effects wouldn't be limited to the header itself.

> Or alternatively we could expand our compiler demands to require
> that redundant typedefs are allowed - I'm not sure there's any animal
> left that doesn't support that (rather than triggering an error it via
> an intentionally set flag).

I'd be happy with that if it actually works, but I strongly suspect
that it does not.  Or can you cite chapter and verse where C99
requires it to work?  My own compiler complains about "redefinition
of typedef 'foo'".

>> I'm really unhappy that force_parallel_mode and
>> parallel_leader_participation are being treated as planner GUCs.
>> They are not that, IMO, because they also affect the behavior of
>> the executor, cf HandleParallelMessage, ExecGather, ExecGatherMerge.
>> This is somewhere between ill-considered and outright broken: what
>> happens if the values change between planning and execution?

> Stylistically I agree with that (and it's what I ended up doing for JIT
> compilation as well), but do you see concrete harm here?

Well, I don't know: I haven't tried to trace the actual effects if
the values change.  Seems like they could be pretty bad, in the worst
case if this wasn't thought through, which it looks like it wasn't.

In any case, if these settings are somehow global rather than being
genuinely planner GUCs, then the planner shouldn't be responsible
for defining them ... maybe they belong in miscadmin.h?

>> I would have exposed estimate_rel_size, which is needed by
>> access/hash/hash.c, except that it requires Relation and
>> BlockNumber typedefs.  The incremental value from keeping
>> hash.c from using plancat.h probably isn't worth widening
>> optimizer.h's #include footprint further.  Also, I wonder
>> whether that whole area needs a rethink for pluggable storage.

> What kind of rejiggering were you thinking of re pluggable storage?

I wasn't; I was just thinking that I didn't want to advertise it
as a stable globally-accessible API if it's likely to get whacked
around soon.

            regards, tom lane


Re: Proposed refactoring of planner header files

От
Andres Freund
Дата:
Hi,

On 2019-01-28 15:50:22 -0500, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > On 2019-01-28 15:17:19 -0500, Tom Lane wrote:
> >> Since I was intentionally trying to limit what optimizer.h pulls in,
> >> and in particular not let it include relation.h, I needed an opaque
> >> typedef for PlannerInfo.  On the other hand relation.h also needs to
> >> typedef that.  I fixed that with a method that we've not used in our
> >> code AFAIK, but is really common in system headers: there's a #define
> >> symbol to remember whether we've created the typedef, and including
> >> both headers in either order will work fine.
> 
> > Ugh, isn't it nicer to just use the underlying struct type instead of
> > that?
> 
> No, because that'd mean that anyplace relying on optimizer.h would also
> have to write "struct PlannerInfo" rather than "PlannerInfo"; the
> effects wouldn't be limited to the header itself.

Why? It can be called with the typedef'd version, or not.  Unless it
calling code has on-stack pointers to it, it ought to never deal with
PlannerInfo vs struct PlannerInfo.  In a lot of cases the code calling
the function will either get the PlannerInfo from somewhere (in which
case that'll either have the typedef'd version or not).


> > Or alternatively we could expand our compiler demands to require
> > that redundant typedefs are allowed - I'm not sure there's any animal
> > left that doesn't support that (rather than triggering an error it via
> > an intentionally set flag).
> 
> I'd be happy with that if it actually works, but I strongly suspect
> that it does not.  Or can you cite chapter and verse where C99
> requires it to work?  My own compiler complains about "redefinition
> of typedef 'foo'".

It's not required by C99, it however is required by C11. But a lot of
compilers have allowed it as an extension for a long time (like before
C99), unless suppressed by some option. I think that's partially because
C++ has allowed it for longer.  I don't know how many of the BF
compilers could be made to accept that - I'd be very suprised if yours couldn't.


> >> I would have exposed estimate_rel_size, which is needed by
> >> access/hash/hash.c, except that it requires Relation and
> >> BlockNumber typedefs.  The incremental value from keeping
> >> hash.c from using plancat.h probably isn't worth widening
> >> optimizer.h's #include footprint further.  Also, I wonder
> >> whether that whole area needs a rethink for pluggable storage.
> 
> > What kind of rejiggering were you thinking of re pluggable storage?
> 
> I wasn't; I was just thinking that I didn't want to advertise it
> as a stable globally-accessible API if it's likely to get whacked
> around soon.

Ah. So far the signature didn't need to change, and I'm not aware of
pending issues requiring it.

Greetings,

Andres Freund


Re: Proposed refactoring of planner header files

От
Andres Freund
Дата:
Hi,

On 2019-01-28 13:02:11 -0800, Andres Freund wrote:
> It's not required by C99, it however is required by C11. But a lot of
> compilers have allowed it as an extension for a long time (like before
> C99), unless suppressed by some option. I think that's partially because
> C++ has allowed it for longer.  I don't know how many of the BF
> compilers could be made to accept that - I'd be very suprised if yours couldn't.

Hm, it's only in gcc 4.6, so that's probably too recent.

Greetings,

Andres Freund


Re: Proposed refactoring of planner header files

От
Tom Lane
Дата:
Andres Freund <andres@anarazel.de> writes:
> On 2019-01-28 15:50:22 -0500, Tom Lane wrote:
>> Andres Freund <andres@anarazel.de> writes:
>>> Ugh, isn't it nicer to just use the underlying struct type instead of
>>> that?

>> No, because that'd mean that anyplace relying on optimizer.h would also
>> have to write "struct PlannerInfo" rather than "PlannerInfo"; the
>> effects wouldn't be limited to the header itself.

> Why? It can be called with the typedef'd version, or not.

Because I don't want users of the header to have to declare, say, local
variables as "struct PlannerInfo *root" instead of "PlannerInfo *root".
The former is not project style and I will not accept forcing that in
order to save a couple of #ifdefs in headers.  I don't actually understand
what you find so ugly about it.

One idea that would save a lot of random "struct foo" hacks in headers
is to allow nodes.h to include "typedef struct MyNode MyNode" for any
recognized node type.  We could either do that just for nodes we've
found it useful for, or pre-emptively for the whole list.

            regards, tom lane


Re: Proposed refactoring of planner header files

От
Tom Lane
Дата:
Andres Freund <andres@anarazel.de> writes:
> On 2019-01-28 13:02:11 -0800, Andres Freund wrote:
>> It's not required by C99, it however is required by C11. But a lot of
>> compilers have allowed it as an extension for a long time (like before
>> C99), unless suppressed by some option.

> Hm, it's only in gcc 4.6, so that's probably too recent.

Yeah, I tried it with RHEL6's gcc 4.4.7, and it doesn't work
(and AFAICS there is no option that would make it work).  A lot
of the buildfarm is running compilers older than that.

I fear we're still 10 years away from being able to demand C11
support ...

            regards, tom lane


Re: Proposed refactoring of planner header files

От
Tom Lane
Дата:
Robert Haas <robertmhaas@gmail.com> writes:
> On Mon, Jan 28, 2019 at 3:17 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> I'm really unhappy that force_parallel_mode and
>> parallel_leader_participation are being treated as planner GUCs.

> The only use of parallel_leader_participation at plan time seems to be
> to twiddle the costing, and the use of it in the executor is to decide
> whether to have the leader participate.  So if the values differ,
> you'll get a plan running a behavior for which plan selection was not
> optimized.  I don't know whether it's useful to intentionally allow
> this so that you can see how the same plan behaves under the other
> setting, or whether it's just a wart we'd be better off without.  It
> might be confusing, though, if you change the setting and it doesn't
> force a replan.

Well, that puts it at the ill-considered end of the spectrum instead
of the outright-broken end, but I still say it's a bad idea.  Planner
GUCs ought to control the produced plan, not other behaviors.

            regards, tom lane


Re: Proposed refactoring of planner header files

От
Tom Lane
Дата:
I wrote:
> ... I didn't work on tightening selfuncs.c's dependencies.
> While I don't have a big problem with considering selfuncs.c to be
> in bed with the planner, that's risky in that whatever dependencies
> selfuncs.c has may well apply to extensions' selectivity estimators too.
> What I'm thinking about doing there is trying to split selfuncs.c into
> two parts, one being infrastructure that can be tightly tied to the
> core planner (and, likely, get moved under backend/optimizer/) and the
> other being estimators that use a limited API and can serve as models
> for extension code.

I spent some time looking at this, wondering whether it'd be practical
to write selectivity-estimating code that hasn't #included pathnodes.h
(nee relation.h).  It seems not: even pretty high-level functions
such as eqjoinsel() need access to fields like RelOptInfo.rows and
SpecialJoinInfo.jointype.  Now, there are only a few such fields, so
conceivably we could provide accessor functions in optimizer.h for
the commonly-used fields and keep the struct types opaque.   I'm not
excited about that though; it's unlike how we do things elsewhere in
Postgres and the mere savings of one #include dependency doesn't seem
to justify it.  So I'm thinking that planner support functions that
want to do selectivity estimation will still end up pulling in
pathnodes.h via selfuncs.h, and we'll just live with that.

However ... there are three pretty clearly identifiable groups of
functions in selfuncs.c: operator-specific estimators, support
functions, and AM-specific indexscan cost estimation functions.
There's a case to be made for splitting them into three separate files.
One point is that selfuncs.c is just too large; at over 8K lines,
it's currently the 7th-largest hand-maintained C file in our tree.
Another point is that as things stand, there's a strong temptation
to bury useful functionality in static functions that can't be
gotten at by extension estimators.  Separating the support functions
might help keep us more honest on that point.  (Or not.)

I'm not sure whether those arguments are strong enough to justify
the added pain-in-the-neck factor from moving a bunch of code
around.  That complicates back-patching bug fixes and it makes it
harder to trace the git history of the code.  So I've got mixed
emotions about whether it's worth doing anything.

Thoughts?

            regards, tom lane


Re: Proposed refactoring of planner header files

От
Robert Haas
Дата:
On Wed, Jan 30, 2019 at 10:08 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> However ... there are three pretty clearly identifiable groups of
> functions in selfuncs.c: operator-specific estimators, support
> functions, and AM-specific indexscan cost estimation functions.
> There's a case to be made for splitting them into three separate files.
> One point is that selfuncs.c is just too large; at over 8K lines,
> it's currently the 7th-largest hand-maintained C file in our tree.
> Another point is that as things stand, there's a strong temptation
> to bury useful functionality in static functions that can't be
> gotten at by extension estimators.  Separating the support functions
> might help keep us more honest on that point.  (Or not.)
>
> I'm not sure whether those arguments are strong enough to justify
> the added pain-in-the-neck factor from moving a bunch of code
> around.  That complicates back-patching bug fixes and it makes it
> harder to trace the git history of the code.  So I've got mixed
> emotions about whether it's worth doing anything.

I think splitting it along these lines would be pretty sensible.  I'm
not really a fan of giant source files, especially if there's no
really good reason to put all that stuff in one source file ... then
again, I'm also not volunteering to do the work.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


Re: Proposed refactoring of planner header files

От
Tom Lane
Дата:
Robert Haas <robertmhaas@gmail.com> writes:
> On Wed, Jan 30, 2019 at 10:08 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> However ... there are three pretty clearly identifiable groups of
>> functions in selfuncs.c: operator-specific estimators, support
>> functions, and AM-specific indexscan cost estimation functions.
>> There's a case to be made for splitting them into three separate files.

> I think splitting it along these lines would be pretty sensible.  I'm
> not really a fan of giant source files, especially if there's no
> really good reason to put all that stuff in one source file ... then
> again, I'm also not volunteering to do the work.

I'm happy to do the work if there's consensus on what to do.  After
a few moments' thought, I'd suggest:

1. Move the indexscan cost estimation functions into a new file
adt/index_selfuncs.c.  Most of them already are declared in
utils/index_selfuncs.h, and we'd move the remaining declarations
(primarily, genericcostestimate()) to that file as well.  This
would affect #includes in contrib/bloom/ and any 3rd-party index
AMs that might be using genericcostestimate().

2a. Leave the support functions in selfuncs.c with declarations in
utils/selfuncs.h, and move the operator-specific estimators to
a new file, perhaps opr_selfuncs.c.  This would minimize pain for
existing includers of selfuncs.h, most of which (particularly outside
core) are presumably interested in the support functions.

2b. Alternatively, leave the operator-specific estimators where they
are, and make new files under optimizer/ for the support functions.

I think 2b would make more sense in the abstract, but it would also
result in more #include churn for extensions.  OTOH we already
forced some churn in that area with the planner-header-refactoring
patch, so the delta cost is probably not that much.

In any case, I'm hoping to get rid of the exported declarations for
pattern selectivity (Pattern_Prefix_Status, pattern_fixed_prefix,
etc) altogether.  Those are exported because indxpath.c currently
calls them from its wired-in lossy-index-clause logic.  When the
dust settles, all that code should be associated with planner
support functions for the LIKE and regex operators, and will not
need to be visible outside of whatever module we put those in.
But that patch is yet to be written.

Opinions?

            regards, tom lane


Re: Proposed refactoring of planner header files

От
Robert Haas
Дата:
On Thu, Jan 31, 2019 at 2:46 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> I'm happy to do the work if there's consensus on what to do.  After
> a few moments' thought, I'd suggest:
>
> 1. Move the indexscan cost estimation functions into a new file
> adt/index_selfuncs.c.  Most of them already are declared in
> utils/index_selfuncs.h, and we'd move the remaining declarations
> (primarily, genericcostestimate()) to that file as well.  This
> would affect #includes in contrib/bloom/ and any 3rd-party index
> AMs that might be using genericcostestimate().
>
> 2a. Leave the support functions in selfuncs.c with declarations in
> utils/selfuncs.h, and move the operator-specific estimators to
> a new file, perhaps opr_selfuncs.c.  This would minimize pain for
> existing includers of selfuncs.h, most of which (particularly outside
> core) are presumably interested in the support functions.
>
> 2b. Alternatively, leave the operator-specific estimators where they
> are, and make new files under optimizer/ for the support functions.

I do not have a powerful opinion on exactly what to do here, but I
offer the following for what it's worth:

- I do not really think much of the selfuncs.c naming.  Nobody who is
not deeply immersed in the PostgreSQL code knows what a "selfunc" is.
Therefore, breaking selfuncs.c into opr_selfuncs.c and
index_selfuncs.c doesn't strike me as the ideal naming choice.  I
would suggest looking for a name that is less steeped in venerable
tradition; perhaps estimator.c or indexsupport.c or selectivity.c or
whatever could be considered for whatever new files we are creating.

- I have always found the placement of selfuncs.c a bit strange: why
is it in utils/adt?  Perhaps there is an argument for having the
things that are specific to a particular type or a particular operator
in that directory, but if so, shouldn't they be grouped with the type
or operator to which they relate, rather than each other?  And if
they're not specific to a certain thing, or if grouping them with that
thing would suck because of what we'd have to #include or some other
grounds, then why not put them in the optimizer?  I think that a large
fraction of this code is actually generic, and putting it in the
optimizer directory someplace would be more logical than what we have
now.  Alternatively, since these have to be exposed via pg_proc, maybe
the shims should go in this directory and the core logic elsewhere.
Again, I don't have a strong opinion here, but I've never thought this
made a ton of sense the way it is.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


Re: Proposed refactoring of planner header files

От
Tom Lane
Дата:
Robert Haas <robertmhaas@gmail.com> writes:
> I do not have a powerful opinion on exactly what to do here, but I
> offer the following for what it's worth:

> - I do not really think much of the selfuncs.c naming.  Nobody who is
> not deeply immersed in the PostgreSQL code knows what a "selfunc" is.
> Therefore, breaking selfuncs.c into opr_selfuncs.c and
> index_selfuncs.c doesn't strike me as the ideal naming choice.  I
> would suggest looking for a name that is less steeped in venerable
> tradition; perhaps estimator.c or indexsupport.c or selectivity.c or
> whatever could be considered for whatever new files we are creating.

Hm.  I'm not really persuaded.  There isn't any part of this code that
isn't "steeped in venerable tradition"; the problem I'm ultimately
trying to fix here can fairly be characterized as 25-year-old technical
debt.  I see your point that adt/ is not where new people might look for
selectivity estimators, but we should also have some sympathy for people
who *do* know the code base expecting to find these functions more or
less where they are.

> - I have always found the placement of selfuncs.c a bit strange: why
> is it in utils/adt?  Perhaps there is an argument for having the
> things that are specific to a particular type or a particular operator
> in that directory, but if so, shouldn't they be grouped with the type
> or operator to which they relate, rather than each other?

We do already have that to some extent:

src/backend/tsearch/ts_selfuncs.c
src/backend/utils/adt/array_selfuncs.c
src/backend/utils/adt/rangetypes_selfuncs.c
src/backend/utils/adt/geo_selfuncs.c
src/backend/utils/adt/network_selfuncs.c
src/backend/utils/adt/selfuncs.c
src/include/utils/index_selfuncs.h
src/include/utils/selfuncs.h
contrib/intarray/_int_selfuncs.c

Breaking up selfuncs.c would carry this a bit further, but giving it
a random new name doesn't seem to me to really improve matters.

Now, if we move the support functions to someplace under optimizer/,
we do have to think about a name for that.  I was considering 
optimizer/utils/selsupport.c and optimizer/selsupport.h, but I'm
certainly not wedded to that.

            regards, tom lane


Re: Proposed refactoring of planner header files

От
Tom Lane
Дата:
Pursuant to this discussion about breaking up selfuncs.c, here's
a proposed patch to shove all the planner logic associated with LIKE
and regex operators into the recently-created like_support.c file.
This takes a bit under 1400 lines out of selfuncs.c.

I was fairly pleased at how neatly this broke up.  I had to export
ineq_histogram_selectivity() and var_eq_const() from selfuncs.c
because some of the moved functions called those; and I also exported
var_eq_non_const() because it seemed to make sense for that to be
visible if var_eq_const() is.  But otherwise there aren't connections
between the two files.

I made pattern_fixed_prefix() and make_greater_string() static in
like_support.c, since all of their callers are there now.  It's
possible that somebody is using those from an extension, in which
case I wouldn't have a big problem with re-exporting them, but
let's wait and see if there's actually any complaints.

This isn't quite purely a code-motion patch: I failed to resist the
temptation to refactor patternsel() slightly so that the LIKE/regex
support functions can call it for SupportRequestSelectivity.  That
means that invoking LIKE/regex through function syntax is exactly
on par with invoking it through operator syntax, so far as the
planner is concerned: not only can we extract lossy index conditions
from something like "WHERE like(textcolumn, 'foo%bar')", but we get
the right selectivity estimate too.  (HEAD can only do the former.)

I should emphasize that I'm not that excited about whether calling
like() as a function is well-supported, and I'd certainly not propose
that we make any large-scale effort to make this work for other
built-in operators.  But I think that extensions like PostGIS may
very well have reason to want their functions and operators to have
parity, so there should be an example in the core code of how to
do it.

Barring objections I'll push this shortly.  I'm not done looking
at selfuncs.c, but this is a separable piece.

            regards, tom lane

diff --git a/src/backend/utils/adt/like_support.c b/src/backend/utils/adt/like_support.c
index b001dde..6950981 100644
--- a/src/backend/utils/adt/like_support.c
+++ b/src/backend/utils/adt/like_support.c
@@ -34,17 +34,39 @@
  */
 #include "postgres.h"

+#include <math.h>
+
+#include "access/htup_details.h"
 #include "access/stratnum.h"
+#include "catalog/pg_collation.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "mb/pg_wchar.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
+#include "utils/datum.h"
 #include "utils/lsyscache.h"
 #include "utils/pg_locale.h"
 #include "utils/selfuncs.h"
+#include "utils/varlena.h"
+
+
+typedef enum
+{
+    Pattern_Type_Like,
+    Pattern_Type_Like_IC,
+    Pattern_Type_Regex,
+    Pattern_Type_Regex_IC,
+    Pattern_Type_Prefix
+} Pattern_Type;

+typedef enum
+{
+    Pattern_Prefix_None, Pattern_Prefix_Partial, Pattern_Prefix_Exact
+} Pattern_Prefix_Status;

 static Node *like_regex_support(Node *rawreq, Pattern_Type ptype);
 static List *match_pattern_prefix(Node *leftop,
@@ -53,6 +75,34 @@ static List *match_pattern_prefix(Node *leftop,
                      Oid expr_coll,
                      Oid opfamily,
                      Oid indexcollation);
+static double patternsel_common(PlannerInfo *root,
+                  Oid oprid,
+                  Oid opfuncid,
+                  List *args,
+                  int varRelid,
+                  Oid collation,
+                  Pattern_Type ptype,
+                  bool negate);
+static Pattern_Prefix_Status pattern_fixed_prefix(Const *patt,
+                     Pattern_Type ptype,
+                     Oid collation,
+                     Const **prefix,
+                     Selectivity *rest_selec);
+static Selectivity prefix_selectivity(PlannerInfo *root,
+                   VariableStatData *vardata,
+                   Oid vartype, Oid opfamily, Const *prefixcon);
+static Selectivity like_selectivity(const char *patt, int pattlen,
+                 bool case_insensitive);
+static Selectivity regex_selectivity(const char *patt, int pattlen,
+                  bool case_insensitive,
+                  int fixed_prefix_len);
+static int pattern_char_isalpha(char c, bool is_multibyte,
+                     pg_locale_t locale, bool locale_is_c);
+static Const *make_greater_string(const Const *str_const, FmgrInfo *ltproc,
+                    Oid collation);
+static Datum string_to_datum(const char *str, Oid datatype);
+static Const *string_to_const(const char *str, Oid datatype);
+static Const *string_to_bytea_const(const char *str, size_t str_len);


 /*
@@ -96,7 +146,39 @@ like_regex_support(Node *rawreq, Pattern_Type ptype)
 {
     Node       *ret = NULL;

-    if (IsA(rawreq, SupportRequestIndexCondition))
+    if (IsA(rawreq, SupportRequestSelectivity))
+    {
+        /*
+         * Make a selectivity estimate for a function call, just as we'd do if
+         * the call was via the corresponding operator.
+         */
+        SupportRequestSelectivity *req = (SupportRequestSelectivity *) rawreq;
+        Selectivity s1;
+
+        if (req->is_join)
+        {
+            /*
+             * For the moment we just punt.  If patternjoinsel is ever
+             * improved to do better, this should be made to call it.
+             */
+            s1 = DEFAULT_MATCH_SEL;
+        }
+        else
+        {
+            /* Share code with operator restriction selectivity functions */
+            s1 = patternsel_common(req->root,
+                                   InvalidOid,
+                                   req->funcid,
+                                   req->args,
+                                   req->varRelid,
+                                   req->inputcollid,
+                                   ptype,
+                                   false);
+        }
+        req->selectivity = s1;
+        ret = (Node *) req;
+    }
+    else if (IsA(rawreq, SupportRequestIndexCondition))
     {
         /* Try to convert operator/function call to index conditions */
         SupportRequestIndexCondition *req = (SupportRequestIndexCondition *) rawreq;
@@ -311,3 +393,1333 @@ match_pattern_prefix(Node *leftop,

     return result;
 }
+
+
+/*
+ * patternsel_common - generic code for pattern-match restriction selectivity.
+ *
+ * To support using this from either the operator or function paths, caller
+ * may pass either operator OID or underlying function OID; we look up the
+ * latter from the former if needed.  (We could just have patternsel() call
+ * get_opcode(), but the work would be wasted if we don't have a need to
+ * compare a fixed prefix to the pg_statistic data.)
+ *
+ * Note that oprid and/or opfuncid should be for the positive-match operator
+ * even when negate is true.
+ */
+static double
+patternsel_common(PlannerInfo *root,
+                  Oid oprid,
+                  Oid opfuncid,
+                  List *args,
+                  int varRelid,
+                  Oid collation,
+                  Pattern_Type ptype,
+                  bool negate)
+{
+    VariableStatData vardata;
+    Node       *other;
+    bool        varonleft;
+    Datum        constval;
+    Oid            consttype;
+    Oid            vartype;
+    Oid            opfamily;
+    Pattern_Prefix_Status pstatus;
+    Const       *patt;
+    Const       *prefix = NULL;
+    Selectivity rest_selec = 0;
+    double        nullfrac = 0.0;
+    double        result;
+
+    /*
+     * Initialize result to the appropriate default estimate depending on
+     * whether it's a match or not-match operator.
+     */
+    if (negate)
+        result = 1.0 - DEFAULT_MATCH_SEL;
+    else
+        result = DEFAULT_MATCH_SEL;
+
+    /*
+     * If expression is not variable op constant, then punt and return the
+     * default estimate.
+     */
+    if (!get_restriction_variable(root, args, varRelid,
+                                  &vardata, &other, &varonleft))
+        return result;
+    if (!varonleft || !IsA(other, Const))
+    {
+        ReleaseVariableStats(vardata);
+        return result;
+    }
+
+    /*
+     * If the constant is NULL, assume operator is strict and return zero, ie,
+     * operator will never return TRUE.  (It's zero even for a negator op.)
+     */
+    if (((Const *) other)->constisnull)
+    {
+        ReleaseVariableStats(vardata);
+        return 0.0;
+    }
+    constval = ((Const *) other)->constvalue;
+    consttype = ((Const *) other)->consttype;
+
+    /*
+     * The right-hand const is type text or bytea for all supported operators.
+     * We do not expect to see binary-compatible types here, since
+     * const-folding should have relabeled the const to exactly match the
+     * operator's declared type.
+     */
+    if (consttype != TEXTOID && consttype != BYTEAOID)
+    {
+        ReleaseVariableStats(vardata);
+        return result;
+    }
+
+    /*
+     * Similarly, the exposed type of the left-hand side should be one of
+     * those we know.  (Do not look at vardata.atttype, which might be
+     * something binary-compatible but different.)    We can use it to choose
+     * the index opfamily from which we must draw the comparison operators.
+     *
+     * NOTE: It would be more correct to use the PATTERN opfamilies than the
+     * simple ones, but at the moment ANALYZE will not generate statistics for
+     * the PATTERN operators.  But our results are so approximate anyway that
+     * it probably hardly matters.
+     */
+    vartype = vardata.vartype;
+
+    switch (vartype)
+    {
+        case TEXTOID:
+        case NAMEOID:
+            opfamily = TEXT_BTREE_FAM_OID;
+            break;
+        case BPCHAROID:
+            opfamily = BPCHAR_BTREE_FAM_OID;
+            break;
+        case BYTEAOID:
+            opfamily = BYTEA_BTREE_FAM_OID;
+            break;
+        default:
+            ReleaseVariableStats(vardata);
+            return result;
+    }
+
+    /*
+     * Grab the nullfrac for use below.
+     */
+    if (HeapTupleIsValid(vardata.statsTuple))
+    {
+        Form_pg_statistic stats;
+
+        stats = (Form_pg_statistic) GETSTRUCT(vardata.statsTuple);
+        nullfrac = stats->stanullfrac;
+    }
+
+    /*
+     * Pull out any fixed prefix implied by the pattern, and estimate the
+     * fractional selectivity of the remainder of the pattern.  Unlike many
+     * other selectivity estimators, we use the pattern operator's actual
+     * collation for this step.  This is not because we expect the collation
+     * to make a big difference in the selectivity estimate (it seldom would),
+     * but because we want to be sure we cache compiled regexps under the
+     * right cache key, so that they can be re-used at runtime.
+     */
+    patt = (Const *) other;
+    pstatus = pattern_fixed_prefix(patt, ptype, collation,
+                                   &prefix, &rest_selec);
+
+    /*
+     * If necessary, coerce the prefix constant to the right type.
+     */
+    if (prefix && prefix->consttype != vartype)
+    {
+        char       *prefixstr;
+
+        switch (prefix->consttype)
+        {
+            case TEXTOID:
+                prefixstr = TextDatumGetCString(prefix->constvalue);
+                break;
+            case BYTEAOID:
+                prefixstr = DatumGetCString(DirectFunctionCall1(byteaout,
+                                                                prefix->constvalue));
+                break;
+            default:
+                elog(ERROR, "unrecognized consttype: %u",
+                     prefix->consttype);
+                ReleaseVariableStats(vardata);
+                return result;
+        }
+        prefix = string_to_const(prefixstr, vartype);
+        pfree(prefixstr);
+    }
+
+    if (pstatus == Pattern_Prefix_Exact)
+    {
+        /*
+         * Pattern specifies an exact match, so pretend operator is '='
+         */
+        Oid            eqopr = get_opfamily_member(opfamily, vartype, vartype,
+                                                BTEqualStrategyNumber);
+
+        if (eqopr == InvalidOid)
+            elog(ERROR, "no = operator for opfamily %u", opfamily);
+        result = var_eq_const(&vardata, eqopr, prefix->constvalue,
+                              false, true, false);
+    }
+    else
+    {
+        /*
+         * Not exact-match pattern.  If we have a sufficiently large
+         * histogram, estimate selectivity for the histogram part of the
+         * population by counting matches in the histogram.  If not, estimate
+         * selectivity of the fixed prefix and remainder of pattern
+         * separately, then combine the two to get an estimate of the
+         * selectivity for the part of the column population represented by
+         * the histogram.  (For small histograms, we combine these
+         * approaches.)
+         *
+         * We then add up data for any most-common-values values; these are
+         * not in the histogram population, and we can get exact answers for
+         * them by applying the pattern operator, so there's no reason to
+         * approximate.  (If the MCVs cover a significant part of the total
+         * population, this gives us a big leg up in accuracy.)
+         */
+        Selectivity selec;
+        int            hist_size;
+        FmgrInfo    opproc;
+        double        mcv_selec,
+                    sumcommon;
+
+        /* Try to use the histogram entries to get selectivity */
+        if (!OidIsValid(opfuncid))
+            opfuncid = get_opcode(oprid);
+        fmgr_info(opfuncid, &opproc);
+
+        selec = histogram_selectivity(&vardata, &opproc, constval, true,
+                                      10, 1, &hist_size);
+
+        /* If not at least 100 entries, use the heuristic method */
+        if (hist_size < 100)
+        {
+            Selectivity heursel;
+            Selectivity prefixsel;
+
+            if (pstatus == Pattern_Prefix_Partial)
+                prefixsel = prefix_selectivity(root, &vardata, vartype,
+                                               opfamily, prefix);
+            else
+                prefixsel = 1.0;
+            heursel = prefixsel * rest_selec;
+
+            if (selec < 0)        /* fewer than 10 histogram entries? */
+                selec = heursel;
+            else
+            {
+                /*
+                 * For histogram sizes from 10 to 100, we combine the
+                 * histogram and heuristic selectivities, putting increasingly
+                 * more trust in the histogram for larger sizes.
+                 */
+                double        hist_weight = hist_size / 100.0;
+
+                selec = selec * hist_weight + heursel * (1.0 - hist_weight);
+            }
+        }
+
+        /* In any case, don't believe extremely small or large estimates. */
+        if (selec < 0.0001)
+            selec = 0.0001;
+        else if (selec > 0.9999)
+            selec = 0.9999;
+
+        /*
+         * If we have most-common-values info, add up the fractions of the MCV
+         * entries that satisfy MCV OP PATTERN.  These fractions contribute
+         * directly to the result selectivity.  Also add up the total fraction
+         * represented by MCV entries.
+         */
+        mcv_selec = mcv_selectivity(&vardata, &opproc, constval, true,
+                                    &sumcommon);
+
+        /*
+         * Now merge the results from the MCV and histogram calculations,
+         * realizing that the histogram covers only the non-null values that
+         * are not listed in MCV.
+         */
+        selec *= 1.0 - nullfrac - sumcommon;
+        selec += mcv_selec;
+        result = selec;
+    }
+
+    /* now adjust if we wanted not-match rather than match */
+    if (negate)
+        result = 1.0 - result - nullfrac;
+
+    /* result should be in range, but make sure... */
+    CLAMP_PROBABILITY(result);
+
+    if (prefix)
+    {
+        pfree(DatumGetPointer(prefix->constvalue));
+        pfree(prefix);
+    }
+
+    ReleaseVariableStats(vardata);
+
+    return result;
+}
+
+/*
+ * Fix impedance mismatch between SQL-callable functions and patternsel_common
+ */
+static double
+patternsel(PG_FUNCTION_ARGS, Pattern_Type ptype, bool negate)
+{
+    PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0);
+    Oid            operator = PG_GETARG_OID(1);
+    List       *args = (List *) PG_GETARG_POINTER(2);
+    int            varRelid = PG_GETARG_INT32(3);
+    Oid            collation = PG_GET_COLLATION();
+
+    /*
+     * If this is for a NOT LIKE or similar operator, get the corresponding
+     * positive-match operator and work with that.
+     */
+    if (negate)
+    {
+        operator = get_negator(operator);
+        if (!OidIsValid(operator))
+            elog(ERROR, "patternsel called for operator without a negator");
+    }
+
+    return patternsel_common(root,
+                             operator,
+                             InvalidOid,
+                             args,
+                             varRelid,
+                             collation,
+                             ptype,
+                             negate);
+}
+
+/*
+ *        regexeqsel        - Selectivity of regular-expression pattern match.
+ */
+Datum
+regexeqsel(PG_FUNCTION_ARGS)
+{
+    PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Regex, false));
+}
+
+/*
+ *        icregexeqsel    - Selectivity of case-insensitive regex match.
+ */
+Datum
+icregexeqsel(PG_FUNCTION_ARGS)
+{
+    PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Regex_IC, false));
+}
+
+/*
+ *        likesel            - Selectivity of LIKE pattern match.
+ */
+Datum
+likesel(PG_FUNCTION_ARGS)
+{
+    PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Like, false));
+}
+
+/*
+ *        prefixsel            - selectivity of prefix operator
+ */
+Datum
+prefixsel(PG_FUNCTION_ARGS)
+{
+    PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Prefix, false));
+}
+
+/*
+ *
+ *        iclikesel            - Selectivity of ILIKE pattern match.
+ */
+Datum
+iclikesel(PG_FUNCTION_ARGS)
+{
+    PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Like_IC, false));
+}
+
+/*
+ *        regexnesel        - Selectivity of regular-expression pattern non-match.
+ */
+Datum
+regexnesel(PG_FUNCTION_ARGS)
+{
+    PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Regex, true));
+}
+
+/*
+ *        icregexnesel    - Selectivity of case-insensitive regex non-match.
+ */
+Datum
+icregexnesel(PG_FUNCTION_ARGS)
+{
+    PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Regex_IC, true));
+}
+
+/*
+ *        nlikesel        - Selectivity of LIKE pattern non-match.
+ */
+Datum
+nlikesel(PG_FUNCTION_ARGS)
+{
+    PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Like, true));
+}
+
+/*
+ *        icnlikesel        - Selectivity of ILIKE pattern non-match.
+ */
+Datum
+icnlikesel(PG_FUNCTION_ARGS)
+{
+    PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Like_IC, true));
+}
+
+/*
+ * patternjoinsel        - Generic code for pattern-match join selectivity.
+ */
+static double
+patternjoinsel(PG_FUNCTION_ARGS, Pattern_Type ptype, bool negate)
+{
+    /* For the moment we just punt. */
+    return negate ? (1.0 - DEFAULT_MATCH_SEL) : DEFAULT_MATCH_SEL;
+}
+
+/*
+ *        regexeqjoinsel    - Join selectivity of regular-expression pattern match.
+ */
+Datum
+regexeqjoinsel(PG_FUNCTION_ARGS)
+{
+    PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Regex, false));
+}
+
+/*
+ *        icregexeqjoinsel    - Join selectivity of case-insensitive regex match.
+ */
+Datum
+icregexeqjoinsel(PG_FUNCTION_ARGS)
+{
+    PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Regex_IC, false));
+}
+
+/*
+ *        likejoinsel            - Join selectivity of LIKE pattern match.
+ */
+Datum
+likejoinsel(PG_FUNCTION_ARGS)
+{
+    PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Like, false));
+}
+
+/*
+ *        prefixjoinsel            - Join selectivity of prefix operator
+ */
+Datum
+prefixjoinsel(PG_FUNCTION_ARGS)
+{
+    PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Prefix, false));
+}
+
+/*
+ *        iclikejoinsel            - Join selectivity of ILIKE pattern match.
+ */
+Datum
+iclikejoinsel(PG_FUNCTION_ARGS)
+{
+    PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Like_IC, false));
+}
+
+/*
+ *        regexnejoinsel    - Join selectivity of regex non-match.
+ */
+Datum
+regexnejoinsel(PG_FUNCTION_ARGS)
+{
+    PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Regex, true));
+}
+
+/*
+ *        icregexnejoinsel    - Join selectivity of case-insensitive regex non-match.
+ */
+Datum
+icregexnejoinsel(PG_FUNCTION_ARGS)
+{
+    PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Regex_IC, true));
+}
+
+/*
+ *        nlikejoinsel        - Join selectivity of LIKE pattern non-match.
+ */
+Datum
+nlikejoinsel(PG_FUNCTION_ARGS)
+{
+    PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Like, true));
+}
+
+/*
+ *        icnlikejoinsel        - Join selectivity of ILIKE pattern non-match.
+ */
+Datum
+icnlikejoinsel(PG_FUNCTION_ARGS)
+{
+    PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Like_IC, true));
+}
+
+
+/*-------------------------------------------------------------------------
+ *
+ * Pattern analysis functions
+ *
+ * These routines support analysis of LIKE and regular-expression patterns
+ * by the planner/optimizer.  It's important that they agree with the
+ * regular-expression code in backend/regex/ and the LIKE code in
+ * backend/utils/adt/like.c.  Also, the computation of the fixed prefix
+ * must be conservative: if we report a string longer than the true fixed
+ * prefix, the query may produce actually wrong answers, rather than just
+ * getting a bad selectivity estimate!
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/*
+ * Extract the fixed prefix, if any, for a pattern.
+ *
+ * *prefix is set to a palloc'd prefix string (in the form of a Const node),
+ *    or to NULL if no fixed prefix exists for the pattern.
+ * If rest_selec is not NULL, *rest_selec is set to an estimate of the
+ *    selectivity of the remainder of the pattern (without any fixed prefix).
+ * The prefix Const has the same type (TEXT or BYTEA) as the input pattern.
+ *
+ * The return value distinguishes no fixed prefix, a partial prefix,
+ * or an exact-match-only pattern.
+ */
+
+static Pattern_Prefix_Status
+like_fixed_prefix(Const *patt_const, bool case_insensitive, Oid collation,
+                  Const **prefix_const, Selectivity *rest_selec)
+{
+    char       *match;
+    char       *patt;
+    int            pattlen;
+    Oid            typeid = patt_const->consttype;
+    int            pos,
+                match_pos;
+    bool        is_multibyte = (pg_database_encoding_max_length() > 1);
+    pg_locale_t locale = 0;
+    bool        locale_is_c = false;
+
+    /* the right-hand const is type text or bytea */
+    Assert(typeid == BYTEAOID || typeid == TEXTOID);
+
+    if (case_insensitive)
+    {
+        if (typeid == BYTEAOID)
+            ereport(ERROR,
+                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                     errmsg("case insensitive matching not supported on type bytea")));
+
+        /* If case-insensitive, we need locale info */
+        if (lc_ctype_is_c(collation))
+            locale_is_c = true;
+        else if (collation != DEFAULT_COLLATION_OID)
+        {
+            if (!OidIsValid(collation))
+            {
+                /*
+                 * This typically means that the parser could not resolve a
+                 * conflict of implicit collations, so report it that way.
+                 */
+                ereport(ERROR,
+                        (errcode(ERRCODE_INDETERMINATE_COLLATION),
+                         errmsg("could not determine which collation to use for ILIKE"),
+                         errhint("Use the COLLATE clause to set the collation explicitly.")));
+            }
+            locale = pg_newlocale_from_collation(collation);
+        }
+    }
+
+    if (typeid != BYTEAOID)
+    {
+        patt = TextDatumGetCString(patt_const->constvalue);
+        pattlen = strlen(patt);
+    }
+    else
+    {
+        bytea       *bstr = DatumGetByteaPP(patt_const->constvalue);
+
+        pattlen = VARSIZE_ANY_EXHDR(bstr);
+        patt = (char *) palloc(pattlen);
+        memcpy(patt, VARDATA_ANY(bstr), pattlen);
+        Assert((Pointer) bstr == DatumGetPointer(patt_const->constvalue));
+    }
+
+    match = palloc(pattlen + 1);
+    match_pos = 0;
+    for (pos = 0; pos < pattlen; pos++)
+    {
+        /* % and _ are wildcard characters in LIKE */
+        if (patt[pos] == '%' ||
+            patt[pos] == '_')
+            break;
+
+        /* Backslash escapes the next character */
+        if (patt[pos] == '\\')
+        {
+            pos++;
+            if (pos >= pattlen)
+                break;
+        }
+
+        /* Stop if case-varying character (it's sort of a wildcard) */
+        if (case_insensitive &&
+            pattern_char_isalpha(patt[pos], is_multibyte, locale, locale_is_c))
+            break;
+
+        match[match_pos++] = patt[pos];
+    }
+
+    match[match_pos] = '\0';
+
+    if (typeid != BYTEAOID)
+        *prefix_const = string_to_const(match, typeid);
+    else
+        *prefix_const = string_to_bytea_const(match, match_pos);
+
+    if (rest_selec != NULL)
+        *rest_selec = like_selectivity(&patt[pos], pattlen - pos,
+                                       case_insensitive);
+
+    pfree(patt);
+    pfree(match);
+
+    /* in LIKE, an empty pattern is an exact match! */
+    if (pos == pattlen)
+        return Pattern_Prefix_Exact;    /* reached end of pattern, so exact */
+
+    if (match_pos > 0)
+        return Pattern_Prefix_Partial;
+
+    return Pattern_Prefix_None;
+}
+
+static Pattern_Prefix_Status
+regex_fixed_prefix(Const *patt_const, bool case_insensitive, Oid collation,
+                   Const **prefix_const, Selectivity *rest_selec)
+{
+    Oid            typeid = patt_const->consttype;
+    char       *prefix;
+    bool        exact;
+
+    /*
+     * Should be unnecessary, there are no bytea regex operators defined. As
+     * such, it should be noted that the rest of this function has *not* been
+     * made safe for binary (possibly NULL containing) strings.
+     */
+    if (typeid == BYTEAOID)
+        ereport(ERROR,
+                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                 errmsg("regular-expression matching not supported on type bytea")));
+
+    /* Use the regexp machinery to extract the prefix, if any */
+    prefix = regexp_fixed_prefix(DatumGetTextPP(patt_const->constvalue),
+                                 case_insensitive, collation,
+                                 &exact);
+
+    if (prefix == NULL)
+    {
+        *prefix_const = NULL;
+
+        if (rest_selec != NULL)
+        {
+            char       *patt = TextDatumGetCString(patt_const->constvalue);
+
+            *rest_selec = regex_selectivity(patt, strlen(patt),
+                                            case_insensitive,
+                                            0);
+            pfree(patt);
+        }
+
+        return Pattern_Prefix_None;
+    }
+
+    *prefix_const = string_to_const(prefix, typeid);
+
+    if (rest_selec != NULL)
+    {
+        if (exact)
+        {
+            /* Exact match, so there's no additional selectivity */
+            *rest_selec = 1.0;
+        }
+        else
+        {
+            char       *patt = TextDatumGetCString(patt_const->constvalue);
+
+            *rest_selec = regex_selectivity(patt, strlen(patt),
+                                            case_insensitive,
+                                            strlen(prefix));
+            pfree(patt);
+        }
+    }
+
+    pfree(prefix);
+
+    if (exact)
+        return Pattern_Prefix_Exact;    /* pattern specifies exact match */
+    else
+        return Pattern_Prefix_Partial;
+}
+
+static Pattern_Prefix_Status
+pattern_fixed_prefix(Const *patt, Pattern_Type ptype, Oid collation,
+                     Const **prefix, Selectivity *rest_selec)
+{
+    Pattern_Prefix_Status result;
+
+    switch (ptype)
+    {
+        case Pattern_Type_Like:
+            result = like_fixed_prefix(patt, false, collation,
+                                       prefix, rest_selec);
+            break;
+        case Pattern_Type_Like_IC:
+            result = like_fixed_prefix(patt, true, collation,
+                                       prefix, rest_selec);
+            break;
+        case Pattern_Type_Regex:
+            result = regex_fixed_prefix(patt, false, collation,
+                                        prefix, rest_selec);
+            break;
+        case Pattern_Type_Regex_IC:
+            result = regex_fixed_prefix(patt, true, collation,
+                                        prefix, rest_selec);
+            break;
+        case Pattern_Type_Prefix:
+            /* Prefix type work is trivial.  */
+            result = Pattern_Prefix_Partial;
+            *rest_selec = 1.0;    /* all */
+            *prefix = makeConst(patt->consttype,
+                                patt->consttypmod,
+                                patt->constcollid,
+                                patt->constlen,
+                                datumCopy(patt->constvalue,
+                                          patt->constbyval,
+                                          patt->constlen),
+                                patt->constisnull,
+                                patt->constbyval);
+            break;
+        default:
+            elog(ERROR, "unrecognized ptype: %d", (int) ptype);
+            result = Pattern_Prefix_None;    /* keep compiler quiet */
+            break;
+    }
+    return result;
+}
+
+/*
+ * Estimate the selectivity of a fixed prefix for a pattern match.
+ *
+ * A fixed prefix "foo" is estimated as the selectivity of the expression
+ * "variable >= 'foo' AND variable < 'fop'" (see also indxpath.c).
+ *
+ * The selectivity estimate is with respect to the portion of the column
+ * population represented by the histogram --- the caller must fold this
+ * together with info about MCVs and NULLs.
+ *
+ * We use the >= and < operators from the specified btree opfamily to do the
+ * estimation.  The given variable and Const must be of the associated
+ * datatype.
+ *
+ * XXX Note: we make use of the upper bound to estimate operator selectivity
+ * even if the locale is such that we cannot rely on the upper-bound string.
+ * The selectivity only needs to be approximately right anyway, so it seems
+ * more useful to use the upper-bound code than not.
+ */
+static Selectivity
+prefix_selectivity(PlannerInfo *root, VariableStatData *vardata,
+                   Oid vartype, Oid opfamily, Const *prefixcon)
+{
+    Selectivity prefixsel;
+    Oid            cmpopr;
+    FmgrInfo    opproc;
+    AttStatsSlot sslot;
+    Const       *greaterstrcon;
+    Selectivity eq_sel;
+
+    cmpopr = get_opfamily_member(opfamily, vartype, vartype,
+                                 BTGreaterEqualStrategyNumber);
+    if (cmpopr == InvalidOid)
+        elog(ERROR, "no >= operator for opfamily %u", opfamily);
+    fmgr_info(get_opcode(cmpopr), &opproc);
+
+    prefixsel = ineq_histogram_selectivity(root, vardata,
+                                           &opproc, true, true,
+                                           prefixcon->constvalue,
+                                           prefixcon->consttype);
+
+    if (prefixsel < 0.0)
+    {
+        /* No histogram is present ... return a suitable default estimate */
+        return DEFAULT_MATCH_SEL;
+    }
+
+    /*-------
+     * If we can create a string larger than the prefix, say
+     * "x < greaterstr".  We try to generate the string referencing the
+     * collation of the var's statistics, but if that's not available,
+     * use DEFAULT_COLLATION_OID.
+     *-------
+     */
+    if (HeapTupleIsValid(vardata->statsTuple) &&
+        get_attstatsslot(&sslot, vardata->statsTuple,
+                         STATISTIC_KIND_HISTOGRAM, InvalidOid, 0))
+         /* sslot.stacoll is set up */ ;
+    else
+        sslot.stacoll = DEFAULT_COLLATION_OID;
+    cmpopr = get_opfamily_member(opfamily, vartype, vartype,
+                                 BTLessStrategyNumber);
+    if (cmpopr == InvalidOid)
+        elog(ERROR, "no < operator for opfamily %u", opfamily);
+    fmgr_info(get_opcode(cmpopr), &opproc);
+    greaterstrcon = make_greater_string(prefixcon, &opproc, sslot.stacoll);
+    if (greaterstrcon)
+    {
+        Selectivity topsel;
+
+        topsel = ineq_histogram_selectivity(root, vardata,
+                                            &opproc, false, false,
+                                            greaterstrcon->constvalue,
+                                            greaterstrcon->consttype);
+
+        /* ineq_histogram_selectivity worked before, it shouldn't fail now */
+        Assert(topsel >= 0.0);
+
+        /*
+         * Merge the two selectivities in the same way as for a range query
+         * (see clauselist_selectivity()).  Note that we don't need to worry
+         * about double-exclusion of nulls, since ineq_histogram_selectivity
+         * doesn't count those anyway.
+         */
+        prefixsel = topsel + prefixsel - 1.0;
+    }
+
+    /*
+     * If the prefix is long then the two bounding values might be too close
+     * together for the histogram to distinguish them usefully, resulting in a
+     * zero estimate (plus or minus roundoff error). To avoid returning a
+     * ridiculously small estimate, compute the estimated selectivity for
+     * "variable = 'foo'", and clamp to that. (Obviously, the resultant
+     * estimate should be at least that.)
+     *
+     * We apply this even if we couldn't make a greater string.  That case
+     * suggests that the prefix is near the maximum possible, and thus
+     * probably off the end of the histogram, and thus we probably got a very
+     * small estimate from the >= condition; so we still need to clamp.
+     */
+    cmpopr = get_opfamily_member(opfamily, vartype, vartype,
+                                 BTEqualStrategyNumber);
+    if (cmpopr == InvalidOid)
+        elog(ERROR, "no = operator for opfamily %u", opfamily);
+    eq_sel = var_eq_const(vardata, cmpopr, prefixcon->constvalue,
+                          false, true, false);
+
+    prefixsel = Max(prefixsel, eq_sel);
+
+    return prefixsel;
+}
+
+
+/*
+ * Estimate the selectivity of a pattern of the specified type.
+ * Note that any fixed prefix of the pattern will have been removed already,
+ * so actually we may be looking at just a fragment of the pattern.
+ *
+ * For now, we use a very simplistic approach: fixed characters reduce the
+ * selectivity a good deal, character ranges reduce it a little,
+ * wildcards (such as % for LIKE or .* for regex) increase it.
+ */
+
+#define FIXED_CHAR_SEL    0.20    /* about 1/5 */
+#define CHAR_RANGE_SEL    0.25
+#define ANY_CHAR_SEL    0.9        /* not 1, since it won't match end-of-string */
+#define FULL_WILDCARD_SEL 5.0
+#define PARTIAL_WILDCARD_SEL 2.0
+
+static Selectivity
+like_selectivity(const char *patt, int pattlen, bool case_insensitive)
+{
+    Selectivity sel = 1.0;
+    int            pos;
+
+    /* Skip any leading wildcard; it's already factored into initial sel */
+    for (pos = 0; pos < pattlen; pos++)
+    {
+        if (patt[pos] != '%' && patt[pos] != '_')
+            break;
+    }
+
+    for (; pos < pattlen; pos++)
+    {
+        /* % and _ are wildcard characters in LIKE */
+        if (patt[pos] == '%')
+            sel *= FULL_WILDCARD_SEL;
+        else if (patt[pos] == '_')
+            sel *= ANY_CHAR_SEL;
+        else if (patt[pos] == '\\')
+        {
+            /* Backslash quotes the next character */
+            pos++;
+            if (pos >= pattlen)
+                break;
+            sel *= FIXED_CHAR_SEL;
+        }
+        else
+            sel *= FIXED_CHAR_SEL;
+    }
+    /* Could get sel > 1 if multiple wildcards */
+    if (sel > 1.0)
+        sel = 1.0;
+    return sel;
+}
+
+static Selectivity
+regex_selectivity_sub(const char *patt, int pattlen, bool case_insensitive)
+{
+    Selectivity sel = 1.0;
+    int            paren_depth = 0;
+    int            paren_pos = 0;    /* dummy init to keep compiler quiet */
+    int            pos;
+
+    for (pos = 0; pos < pattlen; pos++)
+    {
+        if (patt[pos] == '(')
+        {
+            if (paren_depth == 0)
+                paren_pos = pos;    /* remember start of parenthesized item */
+            paren_depth++;
+        }
+        else if (patt[pos] == ')' && paren_depth > 0)
+        {
+            paren_depth--;
+            if (paren_depth == 0)
+                sel *= regex_selectivity_sub(patt + (paren_pos + 1),
+                                             pos - (paren_pos + 1),
+                                             case_insensitive);
+        }
+        else if (patt[pos] == '|' && paren_depth == 0)
+        {
+            /*
+             * If unquoted | is present at paren level 0 in pattern, we have
+             * multiple alternatives; sum their probabilities.
+             */
+            sel += regex_selectivity_sub(patt + (pos + 1),
+                                         pattlen - (pos + 1),
+                                         case_insensitive);
+            break;                /* rest of pattern is now processed */
+        }
+        else if (patt[pos] == '[')
+        {
+            bool        negclass = false;
+
+            if (patt[++pos] == '^')
+            {
+                negclass = true;
+                pos++;
+            }
+            if (patt[pos] == ']')    /* ']' at start of class is not special */
+                pos++;
+            while (pos < pattlen && patt[pos] != ']')
+                pos++;
+            if (paren_depth == 0)
+                sel *= (negclass ? (1.0 - CHAR_RANGE_SEL) : CHAR_RANGE_SEL);
+        }
+        else if (patt[pos] == '.')
+        {
+            if (paren_depth == 0)
+                sel *= ANY_CHAR_SEL;
+        }
+        else if (patt[pos] == '*' ||
+                 patt[pos] == '?' ||
+                 patt[pos] == '+')
+        {
+            /* Ought to be smarter about quantifiers... */
+            if (paren_depth == 0)
+                sel *= PARTIAL_WILDCARD_SEL;
+        }
+        else if (patt[pos] == '{')
+        {
+            while (pos < pattlen && patt[pos] != '}')
+                pos++;
+            if (paren_depth == 0)
+                sel *= PARTIAL_WILDCARD_SEL;
+        }
+        else if (patt[pos] == '\\')
+        {
+            /* backslash quotes the next character */
+            pos++;
+            if (pos >= pattlen)
+                break;
+            if (paren_depth == 0)
+                sel *= FIXED_CHAR_SEL;
+        }
+        else
+        {
+            if (paren_depth == 0)
+                sel *= FIXED_CHAR_SEL;
+        }
+    }
+    /* Could get sel > 1 if multiple wildcards */
+    if (sel > 1.0)
+        sel = 1.0;
+    return sel;
+}
+
+static Selectivity
+regex_selectivity(const char *patt, int pattlen, bool case_insensitive,
+                  int fixed_prefix_len)
+{
+    Selectivity sel;
+
+    /* If patt doesn't end with $, consider it to have a trailing wildcard */
+    if (pattlen > 0 && patt[pattlen - 1] == '$' &&
+        (pattlen == 1 || patt[pattlen - 2] != '\\'))
+    {
+        /* has trailing $ */
+        sel = regex_selectivity_sub(patt, pattlen - 1, case_insensitive);
+    }
+    else
+    {
+        /* no trailing $ */
+        sel = regex_selectivity_sub(patt, pattlen, case_insensitive);
+        sel *= FULL_WILDCARD_SEL;
+    }
+
+    /* If there's a fixed prefix, discount its selectivity */
+    if (fixed_prefix_len > 0)
+        sel /= pow(FIXED_CHAR_SEL, fixed_prefix_len);
+
+    /* Make sure result stays in range */
+    CLAMP_PROBABILITY(sel);
+    return sel;
+}
+
+/*
+ * Check whether char is a letter (and, hence, subject to case-folding)
+ *
+ * In multibyte character sets or with ICU, we can't use isalpha, and it does
+ * not seem worth trying to convert to wchar_t to use iswalpha.  Instead, just
+ * assume any multibyte char is potentially case-varying.
+ */
+static int
+pattern_char_isalpha(char c, bool is_multibyte,
+                     pg_locale_t locale, bool locale_is_c)
+{
+    if (locale_is_c)
+        return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
+    else if (is_multibyte && IS_HIGHBIT_SET(c))
+        return true;
+    else if (locale && locale->provider == COLLPROVIDER_ICU)
+        return IS_HIGHBIT_SET(c) ? true : false;
+#ifdef HAVE_LOCALE_T
+    else if (locale && locale->provider == COLLPROVIDER_LIBC)
+        return isalpha_l((unsigned char) c, locale->info.lt);
+#endif
+    else
+        return isalpha((unsigned char) c);
+}
+
+
+/*
+ * For bytea, the increment function need only increment the current byte
+ * (there are no multibyte characters to worry about).
+ */
+static bool
+byte_increment(unsigned char *ptr, int len)
+{
+    if (*ptr >= 255)
+        return false;
+    (*ptr)++;
+    return true;
+}
+
+/*
+ * Try to generate a string greater than the given string or any
+ * string it is a prefix of.  If successful, return a palloc'd string
+ * in the form of a Const node; else return NULL.
+ *
+ * The caller must provide the appropriate "less than" comparison function
+ * for testing the strings, along with the collation to use.
+ *
+ * The key requirement here is that given a prefix string, say "foo",
+ * we must be able to generate another string "fop" that is greater than
+ * all strings "foobar" starting with "foo".  We can test that we have
+ * generated a string greater than the prefix string, but in non-C collations
+ * that is not a bulletproof guarantee that an extension of the string might
+ * not sort after it; an example is that "foo " is less than "foo!", but it
+ * is not clear that a "dictionary" sort ordering will consider "foo!" less
+ * than "foo bar".  CAUTION: Therefore, this function should be used only for
+ * estimation purposes when working in a non-C collation.
+ *
+ * To try to catch most cases where an extended string might otherwise sort
+ * before the result value, we determine which of the strings "Z", "z", "y",
+ * and "9" is seen as largest by the collation, and append that to the given
+ * prefix before trying to find a string that compares as larger.
+ *
+ * To search for a greater string, we repeatedly "increment" the rightmost
+ * character, using an encoding-specific character incrementer function.
+ * When it's no longer possible to increment the last character, we truncate
+ * off that character and start incrementing the next-to-rightmost.
+ * For example, if "z" were the last character in the sort order, then we
+ * could produce "foo" as a string greater than "fonz".
+ *
+ * This could be rather slow in the worst case, but in most cases we
+ * won't have to try more than one or two strings before succeeding.
+ *
+ * Note that it's important for the character incrementer not to be too anal
+ * about producing every possible character code, since in some cases the only
+ * way to get a larger string is to increment a previous character position.
+ * So we don't want to spend too much time trying every possible character
+ * code at the last position.  A good rule of thumb is to be sure that we
+ * don't try more than 256*K values for a K-byte character (and definitely
+ * not 256^K, which is what an exhaustive search would approach).
+ */
+static Const *
+make_greater_string(const Const *str_const, FmgrInfo *ltproc, Oid collation)
+{
+    Oid            datatype = str_const->consttype;
+    char       *workstr;
+    int            len;
+    Datum        cmpstr;
+    char       *cmptxt = NULL;
+    mbcharacter_incrementer charinc;
+
+    /*
+     * Get a modifiable copy of the prefix string in C-string format, and set
+     * up the string we will compare to as a Datum.  In C locale this can just
+     * be the given prefix string, otherwise we need to add a suffix.  Type
+     * BYTEA sorts bytewise so it never needs a suffix either.
+     */
+    if (datatype == BYTEAOID)
+    {
+        bytea       *bstr = DatumGetByteaPP(str_const->constvalue);
+
+        len = VARSIZE_ANY_EXHDR(bstr);
+        workstr = (char *) palloc(len);
+        memcpy(workstr, VARDATA_ANY(bstr), len);
+        Assert((Pointer) bstr == DatumGetPointer(str_const->constvalue));
+        cmpstr = str_const->constvalue;
+    }
+    else
+    {
+        if (datatype == NAMEOID)
+            workstr = DatumGetCString(DirectFunctionCall1(nameout,
+                                                          str_const->constvalue));
+        else
+            workstr = TextDatumGetCString(str_const->constvalue);
+        len = strlen(workstr);
+        if (lc_collate_is_c(collation) || len == 0)
+            cmpstr = str_const->constvalue;
+        else
+        {
+            /* If first time through, determine the suffix to use */
+            static char suffixchar = 0;
+            static Oid    suffixcollation = 0;
+
+            if (!suffixchar || suffixcollation != collation)
+            {
+                char       *best;
+
+                best = "Z";
+                if (varstr_cmp(best, 1, "z", 1, collation) < 0)
+                    best = "z";
+                if (varstr_cmp(best, 1, "y", 1, collation) < 0)
+                    best = "y";
+                if (varstr_cmp(best, 1, "9", 1, collation) < 0)
+                    best = "9";
+                suffixchar = *best;
+                suffixcollation = collation;
+            }
+
+            /* And build the string to compare to */
+            if (datatype == NAMEOID)
+            {
+                cmptxt = palloc(len + 2);
+                memcpy(cmptxt, workstr, len);
+                cmptxt[len] = suffixchar;
+                cmptxt[len + 1] = '\0';
+                cmpstr = PointerGetDatum(cmptxt);
+            }
+            else
+            {
+                cmptxt = palloc(VARHDRSZ + len + 1);
+                SET_VARSIZE(cmptxt, VARHDRSZ + len + 1);
+                memcpy(VARDATA(cmptxt), workstr, len);
+                *(VARDATA(cmptxt) + len) = suffixchar;
+                cmpstr = PointerGetDatum(cmptxt);
+            }
+        }
+    }
+
+    /* Select appropriate character-incrementer function */
+    if (datatype == BYTEAOID)
+        charinc = byte_increment;
+    else
+        charinc = pg_database_encoding_character_incrementer();
+
+    /* And search ... */
+    while (len > 0)
+    {
+        int            charlen;
+        unsigned char *lastchar;
+
+        /* Identify the last character --- for bytea, just the last byte */
+        if (datatype == BYTEAOID)
+            charlen = 1;
+        else
+            charlen = len - pg_mbcliplen(workstr, len, len - 1);
+        lastchar = (unsigned char *) (workstr + len - charlen);
+
+        /*
+         * Try to generate a larger string by incrementing the last character
+         * (for BYTEA, we treat each byte as a character).
+         *
+         * Note: the incrementer function is expected to return true if it's
+         * generated a valid-per-the-encoding new character, otherwise false.
+         * The contents of the character on false return are unspecified.
+         */
+        while (charinc(lastchar, charlen))
+        {
+            Const       *workstr_const;
+
+            if (datatype == BYTEAOID)
+                workstr_const = string_to_bytea_const(workstr, len);
+            else
+                workstr_const = string_to_const(workstr, datatype);
+
+            if (DatumGetBool(FunctionCall2Coll(ltproc,
+                                               collation,
+                                               cmpstr,
+                                               workstr_const->constvalue)))
+            {
+                /* Successfully made a string larger than cmpstr */
+                if (cmptxt)
+                    pfree(cmptxt);
+                pfree(workstr);
+                return workstr_const;
+            }
+
+            /* No good, release unusable value and try again */
+            pfree(DatumGetPointer(workstr_const->constvalue));
+            pfree(workstr_const);
+        }
+
+        /*
+         * No luck here, so truncate off the last character and try to
+         * increment the next one.
+         */
+        len -= charlen;
+        workstr[len] = '\0';
+    }
+
+    /* Failed... */
+    if (cmptxt)
+        pfree(cmptxt);
+    pfree(workstr);
+
+    return NULL;
+}
+
+/*
+ * Generate a Datum of the appropriate type from a C string.
+ * Note that all of the supported types are pass-by-ref, so the
+ * returned value should be pfree'd if no longer needed.
+ */
+static Datum
+string_to_datum(const char *str, Oid datatype)
+{
+    Assert(str != NULL);
+
+    /*
+     * We cheat a little by assuming that CStringGetTextDatum() will do for
+     * bpchar and varchar constants too...
+     */
+    if (datatype == NAMEOID)
+        return DirectFunctionCall1(namein, CStringGetDatum(str));
+    else if (datatype == BYTEAOID)
+        return DirectFunctionCall1(byteain, CStringGetDatum(str));
+    else
+        return CStringGetTextDatum(str);
+}
+
+/*
+ * Generate a Const node of the appropriate type from a C string.
+ */
+static Const *
+string_to_const(const char *str, Oid datatype)
+{
+    Datum        conval = string_to_datum(str, datatype);
+    Oid            collation;
+    int            constlen;
+
+    /*
+     * We only need to support a few datatypes here, so hard-wire properties
+     * instead of incurring the expense of catalog lookups.
+     */
+    switch (datatype)
+    {
+        case TEXTOID:
+        case VARCHAROID:
+        case BPCHAROID:
+            collation = DEFAULT_COLLATION_OID;
+            constlen = -1;
+            break;
+
+        case NAMEOID:
+            collation = C_COLLATION_OID;
+            constlen = NAMEDATALEN;
+            break;
+
+        case BYTEAOID:
+            collation = InvalidOid;
+            constlen = -1;
+            break;
+
+        default:
+            elog(ERROR, "unexpected datatype in string_to_const: %u",
+                 datatype);
+            return NULL;
+    }
+
+    return makeConst(datatype, -1, collation, constlen,
+                     conval, false, false);
+}
+
+/*
+ * Generate a Const node of bytea type from a binary C string and a length.
+ */
+static Const *
+string_to_bytea_const(const char *str, size_t str_len)
+{
+    bytea       *bstr = palloc(VARHDRSZ + str_len);
+    Datum        conval;
+
+    memcpy(VARDATA(bstr), str, str_len);
+    SET_VARSIZE(bstr, VARHDRSZ + str_len);
+    conval = PointerGetDatum(bstr);
+
+    return makeConst(BYTEAOID, -1, InvalidOid, -1, conval, false, false);
+}
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index b9f99fa..c25357a 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -110,12 +110,9 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_operator.h"
-#include "catalog/pg_opfamily.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_statistic_ext.h"
-#include "catalog/pg_type.h"
 #include "executor/executor.h"
-#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
@@ -125,14 +122,10 @@
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/plancat.h"
-#include "optimizer/restrictinfo.h"
 #include "parser/parse_clause.h"
-#include "parser/parse_coerce.h"
 #include "parser/parsetree.h"
 #include "statistics/statistics.h"
-#include "utils/acl.h"
 #include "utils/builtins.h"
-#include "utils/bytea.h"
 #include "utils/date.h"
 #include "utils/datum.h"
 #include "utils/fmgroids.h"
@@ -146,7 +139,6 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 #include "utils/typcache.h"
-#include "utils/varlena.h"


 /* Hooks for plugins to get control when we ask for stats */
@@ -154,16 +146,6 @@ get_relation_stats_hook_type get_relation_stats_hook = NULL;
 get_index_stats_hook_type get_index_stats_hook = NULL;

 static double eqsel_internal(PG_FUNCTION_ARGS, bool negate);
-static double var_eq_const(VariableStatData *vardata, Oid operator,
-             Datum constval, bool constisnull,
-             bool varonleft, bool negate);
-static double var_eq_non_const(VariableStatData *vardata, Oid operator,
-                 Node *other,
-                 bool varonleft, bool negate);
-static double ineq_histogram_selectivity(PlannerInfo *root,
-                           VariableStatData *vardata,
-                           FmgrInfo *opproc, bool isgt, bool iseq,
-                           Datum constval, Oid consttype);
 static double eqjoinsel_inner(Oid opfuncoid,
                 VariableStatData *vardata1, VariableStatData *vardata2,
                 double nd1, double nd2,
@@ -215,17 +197,6 @@ static bool get_actual_variable_range(PlannerInfo *root,
                           Oid sortop,
                           Datum *min, Datum *max);
 static RelOptInfo *find_join_input_rel(PlannerInfo *root, Relids relids);
-static Selectivity prefix_selectivity(PlannerInfo *root,
-                   VariableStatData *vardata,
-                   Oid vartype, Oid opfamily, Const *prefixcon);
-static Selectivity like_selectivity(const char *patt, int pattlen,
-                 bool case_insensitive);
-static Selectivity regex_selectivity(const char *patt, int pattlen,
-                  bool case_insensitive,
-                  int fixed_prefix_len);
-static Datum string_to_datum(const char *str, Oid datatype);
-static Const *string_to_const(const char *str, Oid datatype);
-static Const *string_to_bytea_const(const char *str, size_t str_len);
 static IndexQualInfo *deconstruct_indexqual(RestrictInfo *rinfo,
                       IndexOptInfo *index, int indexcol);
 static List *add_predicate_to_quals(IndexOptInfo *index, List *indexQuals);
@@ -304,9 +275,9 @@ eqsel_internal(PG_FUNCTION_ARGS, bool negate)
 /*
  * var_eq_const --- eqsel for var = const case
  *
- * This is split out so that some other estimation functions can use it.
+ * This is exported so that some other estimation functions can use it.
  */
-static double
+double
 var_eq_const(VariableStatData *vardata, Oid operator,
              Datum constval, bool constisnull,
              bool varonleft, bool negate)
@@ -457,8 +428,10 @@ var_eq_const(VariableStatData *vardata, Oid operator,

 /*
  * var_eq_non_const --- eqsel for var = something-other-than-const case
+ *
+ * This is exported so that some other estimation functions can use it.
  */
-static double
+double
 var_eq_non_const(VariableStatData *vardata, Oid operator,
                  Node *other,
                  bool varonleft, bool negate)
@@ -784,8 +757,10 @@ histogram_selectivity(VariableStatData *vardata, FmgrInfo *opproc,
  * Note that the result disregards both the most-common-values (if any) and
  * null entries.  The caller is expected to combine this result with
  * statistics for those portions of the column population.
+ *
+ * This is exported so that some other estimation functions can use it.
  */
-static double
+double
 ineq_histogram_selectivity(PlannerInfo *root,
                            VariableStatData *vardata,
                            FmgrInfo *opproc, bool isgt, bool iseq,
@@ -1199,361 +1174,6 @@ scalargesel(PG_FUNCTION_ARGS)
 }

 /*
- * patternsel            - Generic code for pattern-match selectivity.
- */
-static double
-patternsel(PG_FUNCTION_ARGS, Pattern_Type ptype, bool negate)
-{
-    PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0);
-    Oid            operator = PG_GETARG_OID(1);
-    List       *args = (List *) PG_GETARG_POINTER(2);
-    int            varRelid = PG_GETARG_INT32(3);
-    Oid            collation = PG_GET_COLLATION();
-    VariableStatData vardata;
-    Node       *other;
-    bool        varonleft;
-    Datum        constval;
-    Oid            consttype;
-    Oid            vartype;
-    Oid            opfamily;
-    Pattern_Prefix_Status pstatus;
-    Const       *patt;
-    Const       *prefix = NULL;
-    Selectivity rest_selec = 0;
-    double        nullfrac = 0.0;
-    double        result;
-
-    /*
-     * If this is for a NOT LIKE or similar operator, get the corresponding
-     * positive-match operator and work with that.  Set result to the correct
-     * default estimate, too.
-     */
-    if (negate)
-    {
-        operator = get_negator(operator);
-        if (!OidIsValid(operator))
-            elog(ERROR, "patternsel called for operator without a negator");
-        result = 1.0 - DEFAULT_MATCH_SEL;
-    }
-    else
-    {
-        result = DEFAULT_MATCH_SEL;
-    }
-
-    /*
-     * If expression is not variable op constant, then punt and return a
-     * default estimate.
-     */
-    if (!get_restriction_variable(root, args, varRelid,
-                                  &vardata, &other, &varonleft))
-        return result;
-    if (!varonleft || !IsA(other, Const))
-    {
-        ReleaseVariableStats(vardata);
-        return result;
-    }
-
-    /*
-     * If the constant is NULL, assume operator is strict and return zero, ie,
-     * operator will never return TRUE.  (It's zero even for a negator op.)
-     */
-    if (((Const *) other)->constisnull)
-    {
-        ReleaseVariableStats(vardata);
-        return 0.0;
-    }
-    constval = ((Const *) other)->constvalue;
-    consttype = ((Const *) other)->consttype;
-
-    /*
-     * The right-hand const is type text or bytea for all supported operators.
-     * We do not expect to see binary-compatible types here, since
-     * const-folding should have relabeled the const to exactly match the
-     * operator's declared type.
-     */
-    if (consttype != TEXTOID && consttype != BYTEAOID)
-    {
-        ReleaseVariableStats(vardata);
-        return result;
-    }
-
-    /*
-     * Similarly, the exposed type of the left-hand side should be one of
-     * those we know.  (Do not look at vardata.atttype, which might be
-     * something binary-compatible but different.)    We can use it to choose
-     * the index opfamily from which we must draw the comparison operators.
-     *
-     * NOTE: It would be more correct to use the PATTERN opfamilies than the
-     * simple ones, but at the moment ANALYZE will not generate statistics for
-     * the PATTERN operators.  But our results are so approximate anyway that
-     * it probably hardly matters.
-     */
-    vartype = vardata.vartype;
-
-    switch (vartype)
-    {
-        case TEXTOID:
-        case NAMEOID:
-            opfamily = TEXT_BTREE_FAM_OID;
-            break;
-        case BPCHAROID:
-            opfamily = BPCHAR_BTREE_FAM_OID;
-            break;
-        case BYTEAOID:
-            opfamily = BYTEA_BTREE_FAM_OID;
-            break;
-        default:
-            ReleaseVariableStats(vardata);
-            return result;
-    }
-
-    /*
-     * Grab the nullfrac for use below.
-     */
-    if (HeapTupleIsValid(vardata.statsTuple))
-    {
-        Form_pg_statistic stats;
-
-        stats = (Form_pg_statistic) GETSTRUCT(vardata.statsTuple);
-        nullfrac = stats->stanullfrac;
-    }
-
-    /*
-     * Pull out any fixed prefix implied by the pattern, and estimate the
-     * fractional selectivity of the remainder of the pattern.  Unlike many of
-     * the other functions in this file, we use the pattern operator's actual
-     * collation for this step.  This is not because we expect the collation
-     * to make a big difference in the selectivity estimate (it seldom would),
-     * but because we want to be sure we cache compiled regexps under the
-     * right cache key, so that they can be re-used at runtime.
-     */
-    patt = (Const *) other;
-    pstatus = pattern_fixed_prefix(patt, ptype, collation,
-                                   &prefix, &rest_selec);
-
-    /*
-     * If necessary, coerce the prefix constant to the right type.
-     */
-    if (prefix && prefix->consttype != vartype)
-    {
-        char       *prefixstr;
-
-        switch (prefix->consttype)
-        {
-            case TEXTOID:
-                prefixstr = TextDatumGetCString(prefix->constvalue);
-                break;
-            case BYTEAOID:
-                prefixstr = DatumGetCString(DirectFunctionCall1(byteaout,
-                                                                prefix->constvalue));
-                break;
-            default:
-                elog(ERROR, "unrecognized consttype: %u",
-                     prefix->consttype);
-                ReleaseVariableStats(vardata);
-                return result;
-        }
-        prefix = string_to_const(prefixstr, vartype);
-        pfree(prefixstr);
-    }
-
-    if (pstatus == Pattern_Prefix_Exact)
-    {
-        /*
-         * Pattern specifies an exact match, so pretend operator is '='
-         */
-        Oid            eqopr = get_opfamily_member(opfamily, vartype, vartype,
-                                                BTEqualStrategyNumber);
-
-        if (eqopr == InvalidOid)
-            elog(ERROR, "no = operator for opfamily %u", opfamily);
-        result = var_eq_const(&vardata, eqopr, prefix->constvalue,
-                              false, true, false);
-    }
-    else
-    {
-        /*
-         * Not exact-match pattern.  If we have a sufficiently large
-         * histogram, estimate selectivity for the histogram part of the
-         * population by counting matches in the histogram.  If not, estimate
-         * selectivity of the fixed prefix and remainder of pattern
-         * separately, then combine the two to get an estimate of the
-         * selectivity for the part of the column population represented by
-         * the histogram.  (For small histograms, we combine these
-         * approaches.)
-         *
-         * We then add up data for any most-common-values values; these are
-         * not in the histogram population, and we can get exact answers for
-         * them by applying the pattern operator, so there's no reason to
-         * approximate.  (If the MCVs cover a significant part of the total
-         * population, this gives us a big leg up in accuracy.)
-         */
-        Selectivity selec;
-        int            hist_size;
-        FmgrInfo    opproc;
-        double        mcv_selec,
-                    sumcommon;
-
-        /* Try to use the histogram entries to get selectivity */
-        fmgr_info(get_opcode(operator), &opproc);
-
-        selec = histogram_selectivity(&vardata, &opproc, constval, true,
-                                      10, 1, &hist_size);
-
-        /* If not at least 100 entries, use the heuristic method */
-        if (hist_size < 100)
-        {
-            Selectivity heursel;
-            Selectivity prefixsel;
-
-            if (pstatus == Pattern_Prefix_Partial)
-                prefixsel = prefix_selectivity(root, &vardata, vartype,
-                                               opfamily, prefix);
-            else
-                prefixsel = 1.0;
-            heursel = prefixsel * rest_selec;
-
-            if (selec < 0)        /* fewer than 10 histogram entries? */
-                selec = heursel;
-            else
-            {
-                /*
-                 * For histogram sizes from 10 to 100, we combine the
-                 * histogram and heuristic selectivities, putting increasingly
-                 * more trust in the histogram for larger sizes.
-                 */
-                double        hist_weight = hist_size / 100.0;
-
-                selec = selec * hist_weight + heursel * (1.0 - hist_weight);
-            }
-        }
-
-        /* In any case, don't believe extremely small or large estimates. */
-        if (selec < 0.0001)
-            selec = 0.0001;
-        else if (selec > 0.9999)
-            selec = 0.9999;
-
-        /*
-         * If we have most-common-values info, add up the fractions of the MCV
-         * entries that satisfy MCV OP PATTERN.  These fractions contribute
-         * directly to the result selectivity.  Also add up the total fraction
-         * represented by MCV entries.
-         */
-        mcv_selec = mcv_selectivity(&vardata, &opproc, constval, true,
-                                    &sumcommon);
-
-        /*
-         * Now merge the results from the MCV and histogram calculations,
-         * realizing that the histogram covers only the non-null values that
-         * are not listed in MCV.
-         */
-        selec *= 1.0 - nullfrac - sumcommon;
-        selec += mcv_selec;
-        result = selec;
-    }
-
-    /* now adjust if we wanted not-match rather than match */
-    if (negate)
-        result = 1.0 - result - nullfrac;
-
-    /* result should be in range, but make sure... */
-    CLAMP_PROBABILITY(result);
-
-    if (prefix)
-    {
-        pfree(DatumGetPointer(prefix->constvalue));
-        pfree(prefix);
-    }
-
-    ReleaseVariableStats(vardata);
-
-    return result;
-}
-
-/*
- *        regexeqsel        - Selectivity of regular-expression pattern match.
- */
-Datum
-regexeqsel(PG_FUNCTION_ARGS)
-{
-    PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Regex, false));
-}
-
-/*
- *        icregexeqsel    - Selectivity of case-insensitive regex match.
- */
-Datum
-icregexeqsel(PG_FUNCTION_ARGS)
-{
-    PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Regex_IC, false));
-}
-
-/*
- *        likesel            - Selectivity of LIKE pattern match.
- */
-Datum
-likesel(PG_FUNCTION_ARGS)
-{
-    PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Like, false));
-}
-
-/*
- *        prefixsel            - selectivity of prefix operator
- */
-Datum
-prefixsel(PG_FUNCTION_ARGS)
-{
-    PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Prefix, false));
-}
-
-/*
- *
- *        iclikesel            - Selectivity of ILIKE pattern match.
- */
-Datum
-iclikesel(PG_FUNCTION_ARGS)
-{
-    PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Like_IC, false));
-}
-
-/*
- *        regexnesel        - Selectivity of regular-expression pattern non-match.
- */
-Datum
-regexnesel(PG_FUNCTION_ARGS)
-{
-    PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Regex, true));
-}
-
-/*
- *        icregexnesel    - Selectivity of case-insensitive regex non-match.
- */
-Datum
-icregexnesel(PG_FUNCTION_ARGS)
-{
-    PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Regex_IC, true));
-}
-
-/*
- *        nlikesel        - Selectivity of LIKE pattern non-match.
- */
-Datum
-nlikesel(PG_FUNCTION_ARGS)
-{
-    PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Like, true));
-}
-
-/*
- *        icnlikesel        - Selectivity of ILIKE pattern non-match.
- */
-Datum
-icnlikesel(PG_FUNCTION_ARGS)
-{
-    PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Like_IC, true));
-}
-
-/*
  *        boolvarsel        - Selectivity of Boolean variable.
  *
  * This can actually be called on any boolean-valued expression.  If it
@@ -2896,123 +2516,33 @@ scalargejoinsel(PG_FUNCTION_ARGS)
     PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL);
 }

-/*
- * patternjoinsel        - Generic code for pattern-match join selectivity.
- */
-static double
-patternjoinsel(PG_FUNCTION_ARGS, Pattern_Type ptype, bool negate)
-{
-    /* For the moment we just punt. */
-    return negate ? (1.0 - DEFAULT_MATCH_SEL) : DEFAULT_MATCH_SEL;
-}
-
-/*
- *        regexeqjoinsel    - Join selectivity of regular-expression pattern match.
- */
-Datum
-regexeqjoinsel(PG_FUNCTION_ARGS)
-{
-    PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Regex, false));
-}
-
-/*
- *        icregexeqjoinsel    - Join selectivity of case-insensitive regex match.
- */
-Datum
-icregexeqjoinsel(PG_FUNCTION_ARGS)
-{
-    PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Regex_IC, false));
-}
-
-/*
- *        likejoinsel            - Join selectivity of LIKE pattern match.
- */
-Datum
-likejoinsel(PG_FUNCTION_ARGS)
-{
-    PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Like, false));
-}

 /*
- *        prefixjoinsel            - Join selectivity of prefix operator
+ * mergejoinscansel            - Scan selectivity of merge join.
+ *
+ * A merge join will stop as soon as it exhausts either input stream.
+ * Therefore, if we can estimate the ranges of both input variables,
+ * we can estimate how much of the input will actually be read.  This
+ * can have a considerable impact on the cost when using indexscans.
+ *
+ * Also, we can estimate how much of each input has to be read before the
+ * first join pair is found, which will affect the join's startup time.
+ *
+ * clause should be a clause already known to be mergejoinable.  opfamily,
+ * strategy, and nulls_first specify the sort ordering being used.
+ *
+ * The outputs are:
+ *        *leftstart is set to the fraction of the left-hand variable expected
+ *         to be scanned before the first join pair is found (0 to 1).
+ *        *leftend is set to the fraction of the left-hand variable expected
+ *         to be scanned before the join terminates (0 to 1).
+ *        *rightstart, *rightend similarly for the right-hand variable.
  */
-Datum
-prefixjoinsel(PG_FUNCTION_ARGS)
-{
-    PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Prefix, false));
-}
-
-/*
- *        iclikejoinsel            - Join selectivity of ILIKE pattern match.
- */
-Datum
-iclikejoinsel(PG_FUNCTION_ARGS)
-{
-    PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Like_IC, false));
-}
-
-/*
- *        regexnejoinsel    - Join selectivity of regex non-match.
- */
-Datum
-regexnejoinsel(PG_FUNCTION_ARGS)
-{
-    PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Regex, true));
-}
-
-/*
- *        icregexnejoinsel    - Join selectivity of case-insensitive regex non-match.
- */
-Datum
-icregexnejoinsel(PG_FUNCTION_ARGS)
-{
-    PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Regex_IC, true));
-}
-
-/*
- *        nlikejoinsel        - Join selectivity of LIKE pattern non-match.
- */
-Datum
-nlikejoinsel(PG_FUNCTION_ARGS)
-{
-    PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Like, true));
-}
-
-/*
- *        icnlikejoinsel        - Join selectivity of ILIKE pattern non-match.
- */
-Datum
-icnlikejoinsel(PG_FUNCTION_ARGS)
-{
-    PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Like_IC, true));
-}
-
-/*
- * mergejoinscansel            - Scan selectivity of merge join.
- *
- * A merge join will stop as soon as it exhausts either input stream.
- * Therefore, if we can estimate the ranges of both input variables,
- * we can estimate how much of the input will actually be read.  This
- * can have a considerable impact on the cost when using indexscans.
- *
- * Also, we can estimate how much of each input has to be read before the
- * first join pair is found, which will affect the join's startup time.
- *
- * clause should be a clause already known to be mergejoinable.  opfamily,
- * strategy, and nulls_first specify the sort ordering being used.
- *
- * The outputs are:
- *        *leftstart is set to the fraction of the left-hand variable expected
- *         to be scanned before the first join pair is found (0 to 1).
- *        *leftend is set to the fraction of the left-hand variable expected
- *         to be scanned before the join terminates (0 to 1).
- *        *rightstart, *rightend similarly for the right-hand variable.
- */
-void
-mergejoinscansel(PlannerInfo *root, Node *clause,
-                 Oid opfamily, int strategy, bool nulls_first,
-                 Selectivity *leftstart, Selectivity *leftend,
-                 Selectivity *rightstart, Selectivity *rightend)
+void
+mergejoinscansel(PlannerInfo *root, Node *clause,
+                 Oid opfamily, int strategy, bool nulls_first,
+                 Selectivity *leftstart, Selectivity *leftend,
+                 Selectivity *rightstart, Selectivity *rightend)
 {
     Node       *left,
                *right;
@@ -5718,853 +5248,6 @@ find_join_input_rel(PlannerInfo *root, Relids relids)

 /*-------------------------------------------------------------------------
  *
- * Pattern analysis functions
- *
- * These routines support analysis of LIKE and regular-expression patterns
- * by the planner/optimizer.  It's important that they agree with the
- * regular-expression code in backend/regex/ and the LIKE code in
- * backend/utils/adt/like.c.  Also, the computation of the fixed prefix
- * must be conservative: if we report a string longer than the true fixed
- * prefix, the query may produce actually wrong answers, rather than just
- * getting a bad selectivity estimate!
- *
- * Note that the prefix-analysis functions are called from
- * backend/optimizer/path/indxpath.c as well as from routines in this file.
- *
- *-------------------------------------------------------------------------
- */
-
-/*
- * Check whether char is a letter (and, hence, subject to case-folding)
- *
- * In multibyte character sets or with ICU, we can't use isalpha, and it does not seem
- * worth trying to convert to wchar_t to use iswalpha.  Instead, just assume
- * any multibyte char is potentially case-varying.
- */
-static int
-pattern_char_isalpha(char c, bool is_multibyte,
-                     pg_locale_t locale, bool locale_is_c)
-{
-    if (locale_is_c)
-        return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
-    else if (is_multibyte && IS_HIGHBIT_SET(c))
-        return true;
-    else if (locale && locale->provider == COLLPROVIDER_ICU)
-        return IS_HIGHBIT_SET(c) ? true : false;
-#ifdef HAVE_LOCALE_T
-    else if (locale && locale->provider == COLLPROVIDER_LIBC)
-        return isalpha_l((unsigned char) c, locale->info.lt);
-#endif
-    else
-        return isalpha((unsigned char) c);
-}
-
-/*
- * Extract the fixed prefix, if any, for a pattern.
- *
- * *prefix is set to a palloc'd prefix string (in the form of a Const node),
- *    or to NULL if no fixed prefix exists for the pattern.
- * If rest_selec is not NULL, *rest_selec is set to an estimate of the
- *    selectivity of the remainder of the pattern (without any fixed prefix).
- * The prefix Const has the same type (TEXT or BYTEA) as the input pattern.
- *
- * The return value distinguishes no fixed prefix, a partial prefix,
- * or an exact-match-only pattern.
- */
-
-static Pattern_Prefix_Status
-like_fixed_prefix(Const *patt_const, bool case_insensitive, Oid collation,
-                  Const **prefix_const, Selectivity *rest_selec)
-{
-    char       *match;
-    char       *patt;
-    int            pattlen;
-    Oid            typeid = patt_const->consttype;
-    int            pos,
-                match_pos;
-    bool        is_multibyte = (pg_database_encoding_max_length() > 1);
-    pg_locale_t locale = 0;
-    bool        locale_is_c = false;
-
-    /* the right-hand const is type text or bytea */
-    Assert(typeid == BYTEAOID || typeid == TEXTOID);
-
-    if (case_insensitive)
-    {
-        if (typeid == BYTEAOID)
-            ereport(ERROR,
-                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                     errmsg("case insensitive matching not supported on type bytea")));
-
-        /* If case-insensitive, we need locale info */
-        if (lc_ctype_is_c(collation))
-            locale_is_c = true;
-        else if (collation != DEFAULT_COLLATION_OID)
-        {
-            if (!OidIsValid(collation))
-            {
-                /*
-                 * This typically means that the parser could not resolve a
-                 * conflict of implicit collations, so report it that way.
-                 */
-                ereport(ERROR,
-                        (errcode(ERRCODE_INDETERMINATE_COLLATION),
-                         errmsg("could not determine which collation to use for ILIKE"),
-                         errhint("Use the COLLATE clause to set the collation explicitly.")));
-            }
-            locale = pg_newlocale_from_collation(collation);
-        }
-    }
-
-    if (typeid != BYTEAOID)
-    {
-        patt = TextDatumGetCString(patt_const->constvalue);
-        pattlen = strlen(patt);
-    }
-    else
-    {
-        bytea       *bstr = DatumGetByteaPP(patt_const->constvalue);
-
-        pattlen = VARSIZE_ANY_EXHDR(bstr);
-        patt = (char *) palloc(pattlen);
-        memcpy(patt, VARDATA_ANY(bstr), pattlen);
-        Assert((Pointer) bstr == DatumGetPointer(patt_const->constvalue));
-    }
-
-    match = palloc(pattlen + 1);
-    match_pos = 0;
-    for (pos = 0; pos < pattlen; pos++)
-    {
-        /* % and _ are wildcard characters in LIKE */
-        if (patt[pos] == '%' ||
-            patt[pos] == '_')
-            break;
-
-        /* Backslash escapes the next character */
-        if (patt[pos] == '\\')
-        {
-            pos++;
-            if (pos >= pattlen)
-                break;
-        }
-
-        /* Stop if case-varying character (it's sort of a wildcard) */
-        if (case_insensitive &&
-            pattern_char_isalpha(patt[pos], is_multibyte, locale, locale_is_c))
-            break;
-
-        match[match_pos++] = patt[pos];
-    }
-
-    match[match_pos] = '\0';
-
-    if (typeid != BYTEAOID)
-        *prefix_const = string_to_const(match, typeid);
-    else
-        *prefix_const = string_to_bytea_const(match, match_pos);
-
-    if (rest_selec != NULL)
-        *rest_selec = like_selectivity(&patt[pos], pattlen - pos,
-                                       case_insensitive);
-
-    pfree(patt);
-    pfree(match);
-
-    /* in LIKE, an empty pattern is an exact match! */
-    if (pos == pattlen)
-        return Pattern_Prefix_Exact;    /* reached end of pattern, so exact */
-
-    if (match_pos > 0)
-        return Pattern_Prefix_Partial;
-
-    return Pattern_Prefix_None;
-}
-
-static Pattern_Prefix_Status
-regex_fixed_prefix(Const *patt_const, bool case_insensitive, Oid collation,
-                   Const **prefix_const, Selectivity *rest_selec)
-{
-    Oid            typeid = patt_const->consttype;
-    char       *prefix;
-    bool        exact;
-
-    /*
-     * Should be unnecessary, there are no bytea regex operators defined. As
-     * such, it should be noted that the rest of this function has *not* been
-     * made safe for binary (possibly NULL containing) strings.
-     */
-    if (typeid == BYTEAOID)
-        ereport(ERROR,
-                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                 errmsg("regular-expression matching not supported on type bytea")));
-
-    /* Use the regexp machinery to extract the prefix, if any */
-    prefix = regexp_fixed_prefix(DatumGetTextPP(patt_const->constvalue),
-                                 case_insensitive, collation,
-                                 &exact);
-
-    if (prefix == NULL)
-    {
-        *prefix_const = NULL;
-
-        if (rest_selec != NULL)
-        {
-            char       *patt = TextDatumGetCString(patt_const->constvalue);
-
-            *rest_selec = regex_selectivity(patt, strlen(patt),
-                                            case_insensitive,
-                                            0);
-            pfree(patt);
-        }
-
-        return Pattern_Prefix_None;
-    }
-
-    *prefix_const = string_to_const(prefix, typeid);
-
-    if (rest_selec != NULL)
-    {
-        if (exact)
-        {
-            /* Exact match, so there's no additional selectivity */
-            *rest_selec = 1.0;
-        }
-        else
-        {
-            char       *patt = TextDatumGetCString(patt_const->constvalue);
-
-            *rest_selec = regex_selectivity(patt, strlen(patt),
-                                            case_insensitive,
-                                            strlen(prefix));
-            pfree(patt);
-        }
-    }
-
-    pfree(prefix);
-
-    if (exact)
-        return Pattern_Prefix_Exact;    /* pattern specifies exact match */
-    else
-        return Pattern_Prefix_Partial;
-}
-
-Pattern_Prefix_Status
-pattern_fixed_prefix(Const *patt, Pattern_Type ptype, Oid collation,
-                     Const **prefix, Selectivity *rest_selec)
-{
-    Pattern_Prefix_Status result;
-
-    switch (ptype)
-    {
-        case Pattern_Type_Like:
-            result = like_fixed_prefix(patt, false, collation,
-                                       prefix, rest_selec);
-            break;
-        case Pattern_Type_Like_IC:
-            result = like_fixed_prefix(patt, true, collation,
-                                       prefix, rest_selec);
-            break;
-        case Pattern_Type_Regex:
-            result = regex_fixed_prefix(patt, false, collation,
-                                        prefix, rest_selec);
-            break;
-        case Pattern_Type_Regex_IC:
-            result = regex_fixed_prefix(patt, true, collation,
-                                        prefix, rest_selec);
-            break;
-        case Pattern_Type_Prefix:
-            /* Prefix type work is trivial.  */
-            result = Pattern_Prefix_Partial;
-            *rest_selec = 1.0;    /* all */
-            *prefix = makeConst(patt->consttype,
-                                patt->consttypmod,
-                                patt->constcollid,
-                                patt->constlen,
-                                datumCopy(patt->constvalue,
-                                          patt->constbyval,
-                                          patt->constlen),
-                                patt->constisnull,
-                                patt->constbyval);
-            break;
-        default:
-            elog(ERROR, "unrecognized ptype: %d", (int) ptype);
-            result = Pattern_Prefix_None;    /* keep compiler quiet */
-            break;
-    }
-    return result;
-}
-
-/*
- * Estimate the selectivity of a fixed prefix for a pattern match.
- *
- * A fixed prefix "foo" is estimated as the selectivity of the expression
- * "variable >= 'foo' AND variable < 'fop'" (see also indxpath.c).
- *
- * The selectivity estimate is with respect to the portion of the column
- * population represented by the histogram --- the caller must fold this
- * together with info about MCVs and NULLs.
- *
- * We use the >= and < operators from the specified btree opfamily to do the
- * estimation.  The given variable and Const must be of the associated
- * datatype.
- *
- * XXX Note: we make use of the upper bound to estimate operator selectivity
- * even if the locale is such that we cannot rely on the upper-bound string.
- * The selectivity only needs to be approximately right anyway, so it seems
- * more useful to use the upper-bound code than not.
- */
-static Selectivity
-prefix_selectivity(PlannerInfo *root, VariableStatData *vardata,
-                   Oid vartype, Oid opfamily, Const *prefixcon)
-{
-    Selectivity prefixsel;
-    Oid            cmpopr;
-    FmgrInfo    opproc;
-    AttStatsSlot sslot;
-    Const       *greaterstrcon;
-    Selectivity eq_sel;
-
-    cmpopr = get_opfamily_member(opfamily, vartype, vartype,
-                                 BTGreaterEqualStrategyNumber);
-    if (cmpopr == InvalidOid)
-        elog(ERROR, "no >= operator for opfamily %u", opfamily);
-    fmgr_info(get_opcode(cmpopr), &opproc);
-
-    prefixsel = ineq_histogram_selectivity(root, vardata,
-                                           &opproc, true, true,
-                                           prefixcon->constvalue,
-                                           prefixcon->consttype);
-
-    if (prefixsel < 0.0)
-    {
-        /* No histogram is present ... return a suitable default estimate */
-        return DEFAULT_MATCH_SEL;
-    }
-
-    /*-------
-     * If we can create a string larger than the prefix, say
-     * "x < greaterstr".  We try to generate the string referencing the
-     * collation of the var's statistics, but if that's not available,
-     * use DEFAULT_COLLATION_OID.
-     *-------
-     */
-    if (HeapTupleIsValid(vardata->statsTuple) &&
-        get_attstatsslot(&sslot, vardata->statsTuple,
-                         STATISTIC_KIND_HISTOGRAM, InvalidOid, 0))
-         /* sslot.stacoll is set up */ ;
-    else
-        sslot.stacoll = DEFAULT_COLLATION_OID;
-    cmpopr = get_opfamily_member(opfamily, vartype, vartype,
-                                 BTLessStrategyNumber);
-    if (cmpopr == InvalidOid)
-        elog(ERROR, "no < operator for opfamily %u", opfamily);
-    fmgr_info(get_opcode(cmpopr), &opproc);
-    greaterstrcon = make_greater_string(prefixcon, &opproc, sslot.stacoll);
-    if (greaterstrcon)
-    {
-        Selectivity topsel;
-
-        topsel = ineq_histogram_selectivity(root, vardata,
-                                            &opproc, false, false,
-                                            greaterstrcon->constvalue,
-                                            greaterstrcon->consttype);
-
-        /* ineq_histogram_selectivity worked before, it shouldn't fail now */
-        Assert(topsel >= 0.0);
-
-        /*
-         * Merge the two selectivities in the same way as for a range query
-         * (see clauselist_selectivity()).  Note that we don't need to worry
-         * about double-exclusion of nulls, since ineq_histogram_selectivity
-         * doesn't count those anyway.
-         */
-        prefixsel = topsel + prefixsel - 1.0;
-    }
-
-    /*
-     * If the prefix is long then the two bounding values might be too close
-     * together for the histogram to distinguish them usefully, resulting in a
-     * zero estimate (plus or minus roundoff error). To avoid returning a
-     * ridiculously small estimate, compute the estimated selectivity for
-     * "variable = 'foo'", and clamp to that. (Obviously, the resultant
-     * estimate should be at least that.)
-     *
-     * We apply this even if we couldn't make a greater string.  That case
-     * suggests that the prefix is near the maximum possible, and thus
-     * probably off the end of the histogram, and thus we probably got a very
-     * small estimate from the >= condition; so we still need to clamp.
-     */
-    cmpopr = get_opfamily_member(opfamily, vartype, vartype,
-                                 BTEqualStrategyNumber);
-    if (cmpopr == InvalidOid)
-        elog(ERROR, "no = operator for opfamily %u", opfamily);
-    eq_sel = var_eq_const(vardata, cmpopr, prefixcon->constvalue,
-                          false, true, false);
-
-    prefixsel = Max(prefixsel, eq_sel);
-
-    return prefixsel;
-}
-
-
-/*
- * Estimate the selectivity of a pattern of the specified type.
- * Note that any fixed prefix of the pattern will have been removed already,
- * so actually we may be looking at just a fragment of the pattern.
- *
- * For now, we use a very simplistic approach: fixed characters reduce the
- * selectivity a good deal, character ranges reduce it a little,
- * wildcards (such as % for LIKE or .* for regex) increase it.
- */
-
-#define FIXED_CHAR_SEL    0.20    /* about 1/5 */
-#define CHAR_RANGE_SEL    0.25
-#define ANY_CHAR_SEL    0.9        /* not 1, since it won't match end-of-string */
-#define FULL_WILDCARD_SEL 5.0
-#define PARTIAL_WILDCARD_SEL 2.0
-
-static Selectivity
-like_selectivity(const char *patt, int pattlen, bool case_insensitive)
-{
-    Selectivity sel = 1.0;
-    int            pos;
-
-    /* Skip any leading wildcard; it's already factored into initial sel */
-    for (pos = 0; pos < pattlen; pos++)
-    {
-        if (patt[pos] != '%' && patt[pos] != '_')
-            break;
-    }
-
-    for (; pos < pattlen; pos++)
-    {
-        /* % and _ are wildcard characters in LIKE */
-        if (patt[pos] == '%')
-            sel *= FULL_WILDCARD_SEL;
-        else if (patt[pos] == '_')
-            sel *= ANY_CHAR_SEL;
-        else if (patt[pos] == '\\')
-        {
-            /* Backslash quotes the next character */
-            pos++;
-            if (pos >= pattlen)
-                break;
-            sel *= FIXED_CHAR_SEL;
-        }
-        else
-            sel *= FIXED_CHAR_SEL;
-    }
-    /* Could get sel > 1 if multiple wildcards */
-    if (sel > 1.0)
-        sel = 1.0;
-    return sel;
-}
-
-static Selectivity
-regex_selectivity_sub(const char *patt, int pattlen, bool case_insensitive)
-{
-    Selectivity sel = 1.0;
-    int            paren_depth = 0;
-    int            paren_pos = 0;    /* dummy init to keep compiler quiet */
-    int            pos;
-
-    for (pos = 0; pos < pattlen; pos++)
-    {
-        if (patt[pos] == '(')
-        {
-            if (paren_depth == 0)
-                paren_pos = pos;    /* remember start of parenthesized item */
-            paren_depth++;
-        }
-        else if (patt[pos] == ')' && paren_depth > 0)
-        {
-            paren_depth--;
-            if (paren_depth == 0)
-                sel *= regex_selectivity_sub(patt + (paren_pos + 1),
-                                             pos - (paren_pos + 1),
-                                             case_insensitive);
-        }
-        else if (patt[pos] == '|' && paren_depth == 0)
-        {
-            /*
-             * If unquoted | is present at paren level 0 in pattern, we have
-             * multiple alternatives; sum their probabilities.
-             */
-            sel += regex_selectivity_sub(patt + (pos + 1),
-                                         pattlen - (pos + 1),
-                                         case_insensitive);
-            break;                /* rest of pattern is now processed */
-        }
-        else if (patt[pos] == '[')
-        {
-            bool        negclass = false;
-
-            if (patt[++pos] == '^')
-            {
-                negclass = true;
-                pos++;
-            }
-            if (patt[pos] == ']')    /* ']' at start of class is not special */
-                pos++;
-            while (pos < pattlen && patt[pos] != ']')
-                pos++;
-            if (paren_depth == 0)
-                sel *= (negclass ? (1.0 - CHAR_RANGE_SEL) : CHAR_RANGE_SEL);
-        }
-        else if (patt[pos] == '.')
-        {
-            if (paren_depth == 0)
-                sel *= ANY_CHAR_SEL;
-        }
-        else if (patt[pos] == '*' ||
-                 patt[pos] == '?' ||
-                 patt[pos] == '+')
-        {
-            /* Ought to be smarter about quantifiers... */
-            if (paren_depth == 0)
-                sel *= PARTIAL_WILDCARD_SEL;
-        }
-        else if (patt[pos] == '{')
-        {
-            while (pos < pattlen && patt[pos] != '}')
-                pos++;
-            if (paren_depth == 0)
-                sel *= PARTIAL_WILDCARD_SEL;
-        }
-        else if (patt[pos] == '\\')
-        {
-            /* backslash quotes the next character */
-            pos++;
-            if (pos >= pattlen)
-                break;
-            if (paren_depth == 0)
-                sel *= FIXED_CHAR_SEL;
-        }
-        else
-        {
-            if (paren_depth == 0)
-                sel *= FIXED_CHAR_SEL;
-        }
-    }
-    /* Could get sel > 1 if multiple wildcards */
-    if (sel > 1.0)
-        sel = 1.0;
-    return sel;
-}
-
-static Selectivity
-regex_selectivity(const char *patt, int pattlen, bool case_insensitive,
-                  int fixed_prefix_len)
-{
-    Selectivity sel;
-
-    /* If patt doesn't end with $, consider it to have a trailing wildcard */
-    if (pattlen > 0 && patt[pattlen - 1] == '$' &&
-        (pattlen == 1 || patt[pattlen - 2] != '\\'))
-    {
-        /* has trailing $ */
-        sel = regex_selectivity_sub(patt, pattlen - 1, case_insensitive);
-    }
-    else
-    {
-        /* no trailing $ */
-        sel = regex_selectivity_sub(patt, pattlen, case_insensitive);
-        sel *= FULL_WILDCARD_SEL;
-    }
-
-    /* If there's a fixed prefix, discount its selectivity */
-    if (fixed_prefix_len > 0)
-        sel /= pow(FIXED_CHAR_SEL, fixed_prefix_len);
-
-    /* Make sure result stays in range */
-    CLAMP_PROBABILITY(sel);
-    return sel;
-}
-
-
-/*
- * For bytea, the increment function need only increment the current byte
- * (there are no multibyte characters to worry about).
- */
-static bool
-byte_increment(unsigned char *ptr, int len)
-{
-    if (*ptr >= 255)
-        return false;
-    (*ptr)++;
-    return true;
-}
-
-/*
- * Try to generate a string greater than the given string or any
- * string it is a prefix of.  If successful, return a palloc'd string
- * in the form of a Const node; else return NULL.
- *
- * The caller must provide the appropriate "less than" comparison function
- * for testing the strings, along with the collation to use.
- *
- * The key requirement here is that given a prefix string, say "foo",
- * we must be able to generate another string "fop" that is greater than
- * all strings "foobar" starting with "foo".  We can test that we have
- * generated a string greater than the prefix string, but in non-C collations
- * that is not a bulletproof guarantee that an extension of the string might
- * not sort after it; an example is that "foo " is less than "foo!", but it
- * is not clear that a "dictionary" sort ordering will consider "foo!" less
- * than "foo bar".  CAUTION: Therefore, this function should be used only for
- * estimation purposes when working in a non-C collation.
- *
- * To try to catch most cases where an extended string might otherwise sort
- * before the result value, we determine which of the strings "Z", "z", "y",
- * and "9" is seen as largest by the collation, and append that to the given
- * prefix before trying to find a string that compares as larger.
- *
- * To search for a greater string, we repeatedly "increment" the rightmost
- * character, using an encoding-specific character incrementer function.
- * When it's no longer possible to increment the last character, we truncate
- * off that character and start incrementing the next-to-rightmost.
- * For example, if "z" were the last character in the sort order, then we
- * could produce "foo" as a string greater than "fonz".
- *
- * This could be rather slow in the worst case, but in most cases we
- * won't have to try more than one or two strings before succeeding.
- *
- * Note that it's important for the character incrementer not to be too anal
- * about producing every possible character code, since in some cases the only
- * way to get a larger string is to increment a previous character position.
- * So we don't want to spend too much time trying every possible character
- * code at the last position.  A good rule of thumb is to be sure that we
- * don't try more than 256*K values for a K-byte character (and definitely
- * not 256^K, which is what an exhaustive search would approach).
- */
-Const *
-make_greater_string(const Const *str_const, FmgrInfo *ltproc, Oid collation)
-{
-    Oid            datatype = str_const->consttype;
-    char       *workstr;
-    int            len;
-    Datum        cmpstr;
-    char       *cmptxt = NULL;
-    mbcharacter_incrementer charinc;
-
-    /*
-     * Get a modifiable copy of the prefix string in C-string format, and set
-     * up the string we will compare to as a Datum.  In C locale this can just
-     * be the given prefix string, otherwise we need to add a suffix.  Type
-     * BYTEA sorts bytewise so it never needs a suffix either.
-     */
-    if (datatype == BYTEAOID)
-    {
-        bytea       *bstr = DatumGetByteaPP(str_const->constvalue);
-
-        len = VARSIZE_ANY_EXHDR(bstr);
-        workstr = (char *) palloc(len);
-        memcpy(workstr, VARDATA_ANY(bstr), len);
-        Assert((Pointer) bstr == DatumGetPointer(str_const->constvalue));
-        cmpstr = str_const->constvalue;
-    }
-    else
-    {
-        if (datatype == NAMEOID)
-            workstr = DatumGetCString(DirectFunctionCall1(nameout,
-                                                          str_const->constvalue));
-        else
-            workstr = TextDatumGetCString(str_const->constvalue);
-        len = strlen(workstr);
-        if (lc_collate_is_c(collation) || len == 0)
-            cmpstr = str_const->constvalue;
-        else
-        {
-            /* If first time through, determine the suffix to use */
-            static char suffixchar = 0;
-            static Oid    suffixcollation = 0;
-
-            if (!suffixchar || suffixcollation != collation)
-            {
-                char       *best;
-
-                best = "Z";
-                if (varstr_cmp(best, 1, "z", 1, collation) < 0)
-                    best = "z";
-                if (varstr_cmp(best, 1, "y", 1, collation) < 0)
-                    best = "y";
-                if (varstr_cmp(best, 1, "9", 1, collation) < 0)
-                    best = "9";
-                suffixchar = *best;
-                suffixcollation = collation;
-            }
-
-            /* And build the string to compare to */
-            if (datatype == NAMEOID)
-            {
-                cmptxt = palloc(len + 2);
-                memcpy(cmptxt, workstr, len);
-                cmptxt[len] = suffixchar;
-                cmptxt[len + 1] = '\0';
-                cmpstr = PointerGetDatum(cmptxt);
-            }
-            else
-            {
-                cmptxt = palloc(VARHDRSZ + len + 1);
-                SET_VARSIZE(cmptxt, VARHDRSZ + len + 1);
-                memcpy(VARDATA(cmptxt), workstr, len);
-                *(VARDATA(cmptxt) + len) = suffixchar;
-                cmpstr = PointerGetDatum(cmptxt);
-            }
-        }
-    }
-
-    /* Select appropriate character-incrementer function */
-    if (datatype == BYTEAOID)
-        charinc = byte_increment;
-    else
-        charinc = pg_database_encoding_character_incrementer();
-
-    /* And search ... */
-    while (len > 0)
-    {
-        int            charlen;
-        unsigned char *lastchar;
-
-        /* Identify the last character --- for bytea, just the last byte */
-        if (datatype == BYTEAOID)
-            charlen = 1;
-        else
-            charlen = len - pg_mbcliplen(workstr, len, len - 1);
-        lastchar = (unsigned char *) (workstr + len - charlen);
-
-        /*
-         * Try to generate a larger string by incrementing the last character
-         * (for BYTEA, we treat each byte as a character).
-         *
-         * Note: the incrementer function is expected to return true if it's
-         * generated a valid-per-the-encoding new character, otherwise false.
-         * The contents of the character on false return are unspecified.
-         */
-        while (charinc(lastchar, charlen))
-        {
-            Const       *workstr_const;
-
-            if (datatype == BYTEAOID)
-                workstr_const = string_to_bytea_const(workstr, len);
-            else
-                workstr_const = string_to_const(workstr, datatype);
-
-            if (DatumGetBool(FunctionCall2Coll(ltproc,
-                                               collation,
-                                               cmpstr,
-                                               workstr_const->constvalue)))
-            {
-                /* Successfully made a string larger than cmpstr */
-                if (cmptxt)
-                    pfree(cmptxt);
-                pfree(workstr);
-                return workstr_const;
-            }
-
-            /* No good, release unusable value and try again */
-            pfree(DatumGetPointer(workstr_const->constvalue));
-            pfree(workstr_const);
-        }
-
-        /*
-         * No luck here, so truncate off the last character and try to
-         * increment the next one.
-         */
-        len -= charlen;
-        workstr[len] = '\0';
-    }
-
-    /* Failed... */
-    if (cmptxt)
-        pfree(cmptxt);
-    pfree(workstr);
-
-    return NULL;
-}
-
-/*
- * Generate a Datum of the appropriate type from a C string.
- * Note that all of the supported types are pass-by-ref, so the
- * returned value should be pfree'd if no longer needed.
- */
-static Datum
-string_to_datum(const char *str, Oid datatype)
-{
-    Assert(str != NULL);
-
-    /*
-     * We cheat a little by assuming that CStringGetTextDatum() will do for
-     * bpchar and varchar constants too...
-     */
-    if (datatype == NAMEOID)
-        return DirectFunctionCall1(namein, CStringGetDatum(str));
-    else if (datatype == BYTEAOID)
-        return DirectFunctionCall1(byteain, CStringGetDatum(str));
-    else
-        return CStringGetTextDatum(str);
-}
-
-/*
- * Generate a Const node of the appropriate type from a C string.
- */
-static Const *
-string_to_const(const char *str, Oid datatype)
-{
-    Datum        conval = string_to_datum(str, datatype);
-    Oid            collation;
-    int            constlen;
-
-    /*
-     * We only need to support a few datatypes here, so hard-wire properties
-     * instead of incurring the expense of catalog lookups.
-     */
-    switch (datatype)
-    {
-        case TEXTOID:
-        case VARCHAROID:
-        case BPCHAROID:
-            collation = DEFAULT_COLLATION_OID;
-            constlen = -1;
-            break;
-
-        case NAMEOID:
-            collation = C_COLLATION_OID;
-            constlen = NAMEDATALEN;
-            break;
-
-        case BYTEAOID:
-            collation = InvalidOid;
-            constlen = -1;
-            break;
-
-        default:
-            elog(ERROR, "unexpected datatype in string_to_const: %u",
-                 datatype);
-            return NULL;
-    }
-
-    return makeConst(datatype, -1, collation, constlen,
-                     conval, false, false);
-}
-
-/*
- * Generate a Const node of bytea type from a binary C string and a length.
- */
-static Const *
-string_to_bytea_const(const char *str, size_t str_len)
-{
-    bytea       *bstr = palloc(VARHDRSZ + str_len);
-    Datum        conval;
-
-    memcpy(VARDATA(bstr), str, str_len);
-    SET_VARSIZE(bstr, VARHDRSZ + str_len);
-    conval = PointerGetDatum(bstr);
-
-    return makeConst(BYTEAOID, -1, InvalidOid, -1, conval, false, false);
-}
-
-/*-------------------------------------------------------------------------
- *
  * Index cost estimation functions
  *
  *-------------------------------------------------------------------------
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
index 087b56f..8829889 100644
--- a/src/include/utils/selfuncs.h
+++ b/src/include/utils/selfuncs.h
@@ -15,7 +15,6 @@
 #ifndef SELFUNCS_H
 #define SELFUNCS_H

-#include "fmgr.h"
 #include "access/htup.h"
 #include "nodes/pathnodes.h"

@@ -85,20 +84,6 @@ typedef struct VariableStatData
     } while(0)


-typedef enum
-{
-    Pattern_Type_Like,
-    Pattern_Type_Like_IC,
-    Pattern_Type_Regex,
-    Pattern_Type_Regex_IC,
-    Pattern_Type_Prefix
-} Pattern_Type;
-
-typedef enum
-{
-    Pattern_Prefix_None, Pattern_Prefix_Partial, Pattern_Prefix_Exact
-} Pattern_Prefix_Status;
-
 /*
  * deconstruct_indexquals is a simple function to examine the indexquals
  * attached to a proposed IndexPath.  It returns a list of IndexQualInfo
@@ -175,14 +160,16 @@ extern double histogram_selectivity(VariableStatData *vardata, FmgrInfo *opproc,
                       Datum constval, bool varonleft,
                       int min_hist_size, int n_skip,
                       int *hist_size);
-
-extern Pattern_Prefix_Status pattern_fixed_prefix(Const *patt,
-                     Pattern_Type ptype,
-                     Oid collation,
-                     Const **prefix,
-                     Selectivity *rest_selec);
-extern Const *make_greater_string(const Const *str_const, FmgrInfo *ltproc,
-                    Oid collation);
+extern double ineq_histogram_selectivity(PlannerInfo *root,
+                           VariableStatData *vardata,
+                           FmgrInfo *opproc, bool isgt, bool iseq,
+                           Datum constval, Oid consttype);
+extern double var_eq_const(VariableStatData *vardata, Oid oproid,
+             Datum constval, bool constisnull,
+             bool varonleft, bool negate);
+extern double var_eq_non_const(VariableStatData *vardata, Oid oproid,
+                 Node *other,
+                 bool varonleft, bool negate);

 extern Selectivity boolvarsel(PlannerInfo *root, Node *arg, int varRelid);
 extern Selectivity booltestsel(PlannerInfo *root, BoolTestType booltesttype,

Re: Proposed refactoring of planner header files

От
Tom Lane
Дата:
[ moving this discussion to a more appropriate thread; let's keep the
  original thread for discussing whether bloom actually needs fixed ]

Over in
https://www.postgresql.org/message-id/CAMkU=1yHfC+Gu84UFsz4hWn=C7tgQFMLiEQcto1Y-8WDE96vaw@mail.gmail.com

Jeff Janes <jeff.janes@gmail.com> writes:
> On Tue, Feb 12, 2019 at 4:17 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> I'm just in the midst of refactoring that stuff, so if you have
>> suggestions, let's hear 'em.

> The goal would be that I can copy the entire definition of
> genericcostestimate into blcost.c, change the function's name, and get it
> to compile.  I don't know the correct way accomplish that.

The private functions that genericcostestimate needs are
get_index_quals, add_predicate_to_quals, other_operands_eval_cost,
and orderby_operands_eval_cost.  I don't mind exporting those,
although they should get names more appropriate to being global,
and I think the latter two should be merged, as attached.

Another thing that I've been thinking about here is that
deconstruct_indexquals() and the IndexQualInfo struct probably ought
to go away.  That was useful code given our previous convoluted data
structure for index quals, but now its purposes of hiding clause
commutation and associating the right index column with each clause
are vestigial.  The other goal that I originally had in inventing
that code was to allow cost estimation methods to disregard the
details of which clause type each indexqual actually is.  But looking
at how it's being used, it's generally not the case that callers get
to ignore that, which is unsurprising when you think about it: there's
enough behavioral difference between say OpExpr and ScalarArrayOpExpr
that it's kinda silly to think we could really paper it over.

So the attached patch includes a proof-of-concept for getting rid of
IndexQualInfo.  I didn't carry it to completion: gincostestimate is
still using that struct.  The thing that was blocking me was that
given the definition that IndexClause.indexquals is NIL if we're
supposed to use the original clause as-is, it's really hard to write
code that processes all the indexquals without duplicating logic.
That is, you tend to end up with something along the lines of
this, inside a loop over the IndexClauses:

    if (iclause->indexquals == NIL)
        do_something_with(iclause->rinfo);
    else
        foreach(lc, iclause->indexquals)
            do_something_with(lfirst(lc));

If you look at deconstruct_indexquals you'll see that I got around
that by making a subroutine for do_something_with, but it's not
convenient to do that if the code needs access to local variables
in the surrounding function.  In btcostestimate I instead did

    if (iclause->indexquals)
        indexquals = iclause->indexquals;
    else
        indexquals = list_make1(iclause->rinfo);

    foreach(lc, indexquals)
        do_something_with(lfirst(lc));

but I don't like that much either.  It'd be better I think if
we changed the definition of IndexClause.indexquals to always
be nonempty, and just be a singleton list of the original
iclause->rinfo in the simple case.  I'd rejected that approach
to begin with on the grounds that (a) letting it be NIL saves
a list_make1() in the common case, and (b) it seemed a bit
dodgy to have the original clause doubly-linked in this structure.
But argument (a) falls apart completely if places like btcostestimate
are doing their own list_make1 because the data structure is too
hard to work with.  And I don't think (b) holds much water either,
since we doubly-link RestrictInfos all over the place.

So I'm thinking about going and changing the definition of that
data structure before it's set in stone.

Comments?  Does anyone know of third-party AMs that are using
IndexQualInfo and would be sad to have it go away?

            regards, tom lane

diff --git a/contrib/bloom/blcost.c b/contrib/bloom/blcost.c
index 2d8a7f1..f9fe57f 100644
*** a/contrib/bloom/blcost.c
--- b/contrib/bloom/blcost.c
*************** blcostestimate(PlannerInfo *root, IndexP
*** 27,45 ****
                 double *indexPages)
  {
      IndexOptInfo *index = path->indexinfo;
-     List       *qinfos;
      GenericCosts costs;

-     /* Do preliminary analysis of indexquals */
-     qinfos = deconstruct_indexquals(path);
-
      MemSet(&costs, 0, sizeof(costs));

      /* We have to visit all index tuples anyway */
      costs.numIndexTuples = index->tuples;

      /* Use generic estimate */
!     genericcostestimate(root, path, loop_count, qinfos, &costs);

      *indexStartupCost = costs.indexStartupCost;
      *indexTotalCost = costs.indexTotalCost;
--- 27,41 ----
                 double *indexPages)
  {
      IndexOptInfo *index = path->indexinfo;
      GenericCosts costs;

      MemSet(&costs, 0, sizeof(costs));

      /* We have to visit all index tuples anyway */
      costs.numIndexTuples = index->tuples;

      /* Use generic estimate */
!     genericcostestimate(root, path, loop_count, &costs);

      *indexStartupCost = costs.indexStartupCost;
      *indexTotalCost = costs.indexTotalCost;
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index b9f99fa..3e30b57 100644
*** a/src/backend/utils/adt/selfuncs.c
--- b/src/backend/utils/adt/selfuncs.c
*************** static Const *string_to_const(const char
*** 228,234 ****
  static Const *string_to_bytea_const(const char *str, size_t str_len);
  static IndexQualInfo *deconstruct_indexqual(RestrictInfo *rinfo,
                        IndexOptInfo *index, int indexcol);
- static List *add_predicate_to_quals(IndexOptInfo *index, List *indexQuals);


  /*
--- 228,233 ----
*************** string_to_bytea_const(const char *str, s
*** 6570,6578 ****
   *-------------------------------------------------------------------------
   */

! /* Extract the actual indexquals (as RestrictInfos) from an IndexClause list */
! static List *
! get_index_quals(List *indexclauses)
  {
      List       *result = NIL;
      ListCell   *lc;
--- 6569,6579 ----
   *-------------------------------------------------------------------------
   */

! /*
!  * Extract the actual indexquals (as RestrictInfos) from an IndexClause list
!  */
! List *
! get_quals_from_indexclauses(List *indexclauses)
  {
      List       *result = NIL;
      ListCell   *lc;
*************** deconstruct_indexqual(RestrictInfo *rinf
*** 6679,6732 ****
  }

  /*
!  * Simple function to compute the total eval cost of the "other operands"
!  * in an IndexQualInfo list.  Since we know these will be evaluated just
   * once per scan, there's no need to distinguish startup from per-row cost.
-  */
- static Cost
- other_operands_eval_cost(PlannerInfo *root, List *qinfos)
- {
-     Cost        qual_arg_cost = 0;
-     ListCell   *lc;
-
-     foreach(lc, qinfos)
-     {
-         IndexQualInfo *qinfo = (IndexQualInfo *) lfirst(lc);
-         QualCost    index_qual_cost;
-
-         cost_qual_eval_node(&index_qual_cost, qinfo->other_operand, root);
-         qual_arg_cost += index_qual_cost.startup + index_qual_cost.per_tuple;
-     }
-     return qual_arg_cost;
- }
-
- /*
-  * Get other-operand eval cost for an index orderby list.
   *
!  * Index orderby expressions aren't represented as RestrictInfos (since they
!  * aren't boolean, usually).  So we can't apply deconstruct_indexquals to
!  * them.  However, they are much simpler to deal with since they are always
!  * OpExprs and the index column is always on the left.
   */
! static Cost
! orderby_operands_eval_cost(PlannerInfo *root, IndexPath *path)
  {
      Cost        qual_arg_cost = 0;
      ListCell   *lc;

!     foreach(lc, path->indexorderbys)
      {
          Expr       *clause = (Expr *) lfirst(lc);
          Node       *other_operand;
          QualCost    index_qual_cost;

          if (IsA(clause, OpExpr))
          {
!             other_operand = get_rightop(clause);
          }
          else
          {
!             elog(ERROR, "unsupported indexorderby type: %d",
                   (int) nodeTag(clause));
              other_operand = NULL;    /* keep compiler quiet */
          }
--- 6680,6737 ----
  }

  /*
!  * Compute the total evaluation cost of the comparison operands in a list
!  * of index qual expressions.  Since we know these will be evaluated just
   * once per scan, there's no need to distinguish startup from per-row cost.
   *
!  * This can be used either on the result of get_quals_from_indexclauses(),
!  * or directly on an indexorderbys list.  In both cases, we expect that the
!  * index key expression is on the left side of binary clauses.
   */
! Cost
! index_other_operands_eval_cost(PlannerInfo *root, List *indexquals)
  {
      Cost        qual_arg_cost = 0;
      ListCell   *lc;

!     foreach(lc, indexquals)
      {
          Expr       *clause = (Expr *) lfirst(lc);
          Node       *other_operand;
          QualCost    index_qual_cost;

+         /*
+          * Index quals will have RestrictInfos, indexorderbys won't.  Look
+          * through RestrictInfo if present.
+          */
+         if (IsA(clause, RestrictInfo))
+             clause = ((RestrictInfo *) clause)->clause;
+
          if (IsA(clause, OpExpr))
          {
!             OpExpr       *op = (OpExpr *) clause;
!
!             other_operand = (Node *) lsecond(op->args);
!         }
!         else if (IsA(clause, RowCompareExpr))
!         {
!             RowCompareExpr *rc = (RowCompareExpr *) clause;
!
!             other_operand = (Node *) rc->rargs;
!         }
!         else if (IsA(clause, ScalarArrayOpExpr))
!         {
!             ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
!
!             other_operand = (Node *) lsecond(saop->args);
!         }
!         else if (IsA(clause, NullTest))
!         {
!             other_operand = NULL;
          }
          else
          {
!             elog(ERROR, "unsupported indexqual type: %d",
                   (int) nodeTag(clause));
              other_operand = NULL;    /* keep compiler quiet */
          }
*************** void
*** 6741,6751 ****
  genericcostestimate(PlannerInfo *root,
                      IndexPath *path,
                      double loop_count,
-                     List *qinfos,
                      GenericCosts *costs)
  {
      IndexOptInfo *index = path->indexinfo;
!     List       *indexQuals = get_index_quals(path->indexclauses);
      List       *indexOrderBys = path->indexorderbys;
      Cost        indexStartupCost;
      Cost        indexTotalCost;
--- 6746,6755 ----
  genericcostestimate(PlannerInfo *root,
                      IndexPath *path,
                      double loop_count,
                      GenericCosts *costs)
  {
      IndexOptInfo *index = path->indexinfo;
!     List       *indexQuals = get_quals_from_indexclauses(path->indexclauses);
      List       *indexOrderBys = path->indexorderbys;
      Cost        indexStartupCost;
      Cost        indexTotalCost;
*************** genericcostestimate(PlannerInfo *root,
*** 6767,6773 ****
       * given indexquals to produce a more accurate idea of the index
       * selectivity.
       */
!     selectivityQuals = add_predicate_to_quals(index, indexQuals);

      /*
       * Check for ScalarArrayOpExpr index quals, and estimate the number of
--- 6771,6777 ----
       * given indexquals to produce a more accurate idea of the index
       * selectivity.
       */
!     selectivityQuals = add_predicate_to_index_quals(index, indexQuals);

      /*
       * Check for ScalarArrayOpExpr index quals, and estimate the number of
*************** genericcostestimate(PlannerInfo *root,
*** 6910,6917 ****
       * Detecting that that might be needed seems more expensive than it's
       * worth, though, considering all the other inaccuracies here ...
       */
!     qual_arg_cost = other_operands_eval_cost(root, qinfos) +
!         orderby_operands_eval_cost(root, path);
      qual_op_cost = cpu_operator_cost *
          (list_length(indexQuals) + list_length(indexOrderBys));

--- 6914,6921 ----
       * Detecting that that might be needed seems more expensive than it's
       * worth, though, considering all the other inaccuracies here ...
       */
!     qual_arg_cost = index_other_operands_eval_cost(root, indexQuals) +
!         index_other_operands_eval_cost(root, indexOrderBys);
      qual_op_cost = cpu_operator_cost *
          (list_length(indexQuals) + list_length(indexOrderBys));

*************** genericcostestimate(PlannerInfo *root,
*** 6956,6963 ****
   * predicate_implied_by() and clauselist_selectivity(), but might be
   * problematic if the result were passed to other things.
   */
! static List *
! add_predicate_to_quals(IndexOptInfo *index, List *indexQuals)
  {
      List       *predExtraQuals = NIL;
      ListCell   *lc;
--- 6960,6967 ----
   * predicate_implied_by() and clauselist_selectivity(), but might be
   * problematic if the result were passed to other things.
   */
! List *
! add_predicate_to_index_quals(IndexOptInfo *index, List *indexQuals)
  {
      List       *predExtraQuals = NIL;
      ListCell   *lc;
*************** btcostestimate(PlannerInfo *root, IndexP
*** 6985,6991 ****
                 double *indexPages)
  {
      IndexOptInfo *index = path->indexinfo;
-     List       *qinfos;
      GenericCosts costs;
      Oid            relid;
      AttrNumber    colnum;
--- 6989,6994 ----
*************** btcostestimate(PlannerInfo *root, IndexP
*** 7000,7008 ****
      double        num_sa_scans;
      ListCell   *lc;

-     /* Do preliminary analysis of indexquals */
-     qinfos = deconstruct_indexquals(path);
-
      /*
       * For a btree scan, only leading '=' quals plus inequality quals for the
       * immediately next attribute contribute to index selectivity (these are
--- 7003,7008 ----
*************** btcostestimate(PlannerInfo *root, IndexP
*** 7026,7083 ****
      found_saop = false;
      found_is_null_op = false;
      num_sa_scans = 1;
!     foreach(lc, qinfos)
      {
!         IndexQualInfo *qinfo = (IndexQualInfo *) lfirst(lc);
!         RestrictInfo *rinfo = qinfo->rinfo;
!         Expr       *clause = rinfo->clause;
!         Oid            clause_op;
!         int            op_strategy;

!         if (indexcol != qinfo->indexcol)
          {
              /* Beginning of a new column's quals */
              if (!eqQualHere)
                  break;            /* done if no '=' qual for indexcol */
              eqQualHere = false;
              indexcol++;
!             if (indexcol != qinfo->indexcol)
                  break;            /* no quals at all for indexcol */
          }

!         if (IsA(clause, ScalarArrayOpExpr))
!         {
!             int            alength = estimate_array_length(qinfo->other_operand);

!             found_saop = true;
!             /* count up number of SA scans induced by indexBoundQuals only */
!             if (alength > 1)
!                 num_sa_scans *= alength;
!         }
!         else if (IsA(clause, NullTest))
          {
!             NullTest   *nt = (NullTest *) clause;

!             if (nt->nulltesttype == IS_NULL)
              {
!                 found_is_null_op = true;
!                 /* IS NULL is like = for selectivity determination purposes */
!                 eqQualHere = true;
              }
!         }

!         /* check for equality operator */
!         clause_op = qinfo->clause_op;
!         if (OidIsValid(clause_op))
!         {
!             op_strategy = get_op_opfamily_strategy(clause_op,
!                                                    index->opfamily[indexcol]);
!             Assert(op_strategy != 0);    /* not a member of opfamily?? */
!             if (op_strategy == BTEqualStrategyNumber)
!                 eqQualHere = true;
!         }

!         indexBoundQuals = lappend(indexBoundQuals, rinfo);
      }

      /*
--- 7026,7109 ----
      found_saop = false;
      found_is_null_op = false;
      num_sa_scans = 1;
!     foreach(lc, path->indexclauses)
      {
!         IndexClause *iclause = lfirst_node(IndexClause, lc);
!         List       *indexquals;
!         ListCell   *lc2;

!         if (indexcol != iclause->indexcol)
          {
              /* Beginning of a new column's quals */
              if (!eqQualHere)
                  break;            /* done if no '=' qual for indexcol */
              eqQualHere = false;
              indexcol++;
!             if (indexcol != iclause->indexcol)
                  break;            /* no quals at all for indexcol */
          }

!         /* Examine each indexqual associated with this index clause */
!         if (iclause->indexquals)
!             indexquals = iclause->indexquals;
!         else
!             indexquals = list_make1(iclause->rinfo);

!         foreach(lc2, indexquals)
          {
!             RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc2);
!             Expr       *clause = rinfo->clause;
!             Oid            clause_op = InvalidOid;
!             int            op_strategy;

!             if (IsA(clause, OpExpr))
              {
!                 OpExpr       *op = (OpExpr *) clause;
!
!                 clause_op = op->opno;
              }
!             else if (IsA(clause, RowCompareExpr))
!             {
!                 RowCompareExpr *rc = (RowCompareExpr *) clause;

!                 clause_op = linitial_oid(rc->opnos);
!             }
!             else if (IsA(clause, ScalarArrayOpExpr))
!             {
!                 ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
!                 Node       *other_operand = (Node *) lsecond(saop->args);
!                 int            alength = estimate_array_length(other_operand);

!                 clause_op = saop->opno;
!                 found_saop = true;
!                 /* count number of SA scans induced by indexBoundQuals only */
!                 if (alength > 1)
!                     num_sa_scans *= alength;
!             }
!             else if (IsA(clause, NullTest))
!             {
!                 NullTest   *nt = (NullTest *) clause;
!
!                 if (nt->nulltesttype == IS_NULL)
!                 {
!                     found_is_null_op = true;
!                     /* IS NULL is like = for selectivity purposes */
!                     eqQualHere = true;
!                 }
!             }
!
!             /* check for equality operator */
!             if (OidIsValid(clause_op))
!             {
!                 op_strategy = get_op_opfamily_strategy(clause_op,
!                                                        index->opfamily[indexcol]);
!                 Assert(op_strategy != 0);    /* not a member of opfamily?? */
!                 if (op_strategy == BTEqualStrategyNumber)
!                     eqQualHere = true;
!             }
!
!             indexBoundQuals = lappend(indexBoundQuals, rinfo);
!         }
      }

      /*
*************** btcostestimate(PlannerInfo *root, IndexP
*** 7102,7108 ****
           * index-bound quals to produce a more accurate idea of the number of
           * rows covered by the bound conditions.
           */
!         selectivityQuals = add_predicate_to_quals(index, indexBoundQuals);

          btreeSelectivity = clauselist_selectivity(root, selectivityQuals,
                                                    index->rel->relid,
--- 7128,7134 ----
           * index-bound quals to produce a more accurate idea of the number of
           * rows covered by the bound conditions.
           */
!         selectivityQuals = add_predicate_to_index_quals(index, indexBoundQuals);

          btreeSelectivity = clauselist_selectivity(root, selectivityQuals,
                                                    index->rel->relid,
*************** btcostestimate(PlannerInfo *root, IndexP
*** 7124,7130 ****
      MemSet(&costs, 0, sizeof(costs));
      costs.numIndexTuples = numIndexTuples;

!     genericcostestimate(root, path, loop_count, qinfos, &costs);

      /*
       * Add a CPU-cost component to represent the costs of initial btree
--- 7150,7156 ----
      MemSet(&costs, 0, sizeof(costs));
      costs.numIndexTuples = numIndexTuples;

!     genericcostestimate(root, path, loop_count, &costs);

      /*
       * Add a CPU-cost component to represent the costs of initial btree
*************** hashcostestimate(PlannerInfo *root, Inde
*** 7271,7285 ****
                   Selectivity *indexSelectivity, double *indexCorrelation,
                   double *indexPages)
  {
-     List       *qinfos;
      GenericCosts costs;

-     /* Do preliminary analysis of indexquals */
-     qinfos = deconstruct_indexquals(path);
-
      MemSet(&costs, 0, sizeof(costs));

!     genericcostestimate(root, path, loop_count, qinfos, &costs);

      /*
       * A hash index has no descent costs as such, since the index AM can go
--- 7297,7307 ----
                   Selectivity *indexSelectivity, double *indexCorrelation,
                   double *indexPages)
  {
      GenericCosts costs;

      MemSet(&costs, 0, sizeof(costs));

!     genericcostestimate(root, path, loop_count, &costs);

      /*
       * A hash index has no descent costs as such, since the index AM can go
*************** gistcostestimate(PlannerInfo *root, Inde
*** 7320,7335 ****
                   double *indexPages)
  {
      IndexOptInfo *index = path->indexinfo;
-     List       *qinfos;
      GenericCosts costs;
      Cost        descentCost;

-     /* Do preliminary analysis of indexquals */
-     qinfos = deconstruct_indexquals(path);
-
      MemSet(&costs, 0, sizeof(costs));

!     genericcostestimate(root, path, loop_count, qinfos, &costs);

      /*
       * We model index descent costs similarly to those for btree, but to do
--- 7342,7353 ----
                   double *indexPages)
  {
      IndexOptInfo *index = path->indexinfo;
      GenericCosts costs;
      Cost        descentCost;

      MemSet(&costs, 0, sizeof(costs));

!     genericcostestimate(root, path, loop_count, &costs);

      /*
       * We model index descent costs similarly to those for btree, but to do
*************** spgcostestimate(PlannerInfo *root, Index
*** 7381,7396 ****
                  double *indexPages)
  {
      IndexOptInfo *index = path->indexinfo;
-     List       *qinfos;
      GenericCosts costs;
      Cost        descentCost;

-     /* Do preliminary analysis of indexquals */
-     qinfos = deconstruct_indexquals(path);
-
      MemSet(&costs, 0, sizeof(costs));

!     genericcostestimate(root, path, loop_count, qinfos, &costs);

      /*
       * We model index descent costs similarly to those for btree, but to do
--- 7399,7410 ----
                  double *indexPages)
  {
      IndexOptInfo *index = path->indexinfo;
      GenericCosts costs;
      Cost        descentCost;

      MemSet(&costs, 0, sizeof(costs));

!     genericcostestimate(root, path, loop_count, &costs);

      /*
       * We model index descent costs similarly to those for btree, but to do
*************** gincostestimate(PlannerInfo *root, Index
*** 7730,7737 ****
                  double *indexPages)
  {
      IndexOptInfo *index = path->indexinfo;
!     List       *indexQuals = get_index_quals(path->indexclauses);
!     List       *indexOrderBys = path->indexorderbys;
      List       *qinfos;
      ListCell   *l;
      List       *selectivityQuals;
--- 7744,7750 ----
                  double *indexPages)
  {
      IndexOptInfo *index = path->indexinfo;
!     List       *indexQuals = get_quals_from_indexclauses(path->indexclauses);
      List       *qinfos;
      ListCell   *l;
      List       *selectivityQuals;
*************** gincostestimate(PlannerInfo *root, Index
*** 7837,7843 ****
       * quals to produce a more accurate idea of the number of rows covered by
       * the bound conditions.
       */
!     selectivityQuals = add_predicate_to_quals(index, indexQuals);

      /* Estimate the fraction of main-table tuples that will be visited */
      *indexSelectivity = clauselist_selectivity(root, selectivityQuals,
--- 7850,7856 ----
       * quals to produce a more accurate idea of the number of rows covered by
       * the bound conditions.
       */
!     selectivityQuals = add_predicate_to_index_quals(index, indexQuals);

      /* Estimate the fraction of main-table tuples that will be visited */
      *indexSelectivity = clauselist_selectivity(root, selectivityQuals,
*************** gincostestimate(PlannerInfo *root, Index
*** 8017,8028 ****
          dataPagesFetched * spc_random_page_cost;

      /*
!      * Add on index qual eval costs, much as in genericcostestimate
       */
!     qual_arg_cost = other_operands_eval_cost(root, qinfos) +
!         orderby_operands_eval_cost(root, path);
!     qual_op_cost = cpu_operator_cost *
!         (list_length(indexQuals) + list_length(indexOrderBys));

      *indexStartupCost += qual_arg_cost;
      *indexTotalCost += qual_arg_cost;
--- 8030,8040 ----
          dataPagesFetched * spc_random_page_cost;

      /*
!      * Add on index qual eval costs, much as in genericcostestimate.  But we
!      * can disregard indexorderbys, since GIN doesn't support those.
       */
!     qual_arg_cost = index_other_operands_eval_cost(root, indexQuals);
!     qual_op_cost = cpu_operator_cost * list_length(indexQuals);

      *indexStartupCost += qual_arg_cost;
      *indexTotalCost += qual_arg_cost;
*************** brincostestimate(PlannerInfo *root, Inde
*** 8040,8050 ****
                   double *indexPages)
  {
      IndexOptInfo *index = path->indexinfo;
!     List       *indexQuals = get_index_quals(path->indexclauses);
      double        numPages = index->pages;
      RelOptInfo *baserel = index->rel;
      RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
-     List       *qinfos;
      Cost        spc_seq_page_cost;
      Cost        spc_random_page_cost;
      double        qual_arg_cost;
--- 8052,8061 ----
                   double *indexPages)
  {
      IndexOptInfo *index = path->indexinfo;
!     List       *indexQuals = get_quals_from_indexclauses(path->indexclauses);
      double        numPages = index->pages;
      RelOptInfo *baserel = index->rel;
      RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
      Cost        spc_seq_page_cost;
      Cost        spc_random_page_cost;
      double        qual_arg_cost;
*************** brincostestimate(PlannerInfo *root, Inde
*** 8082,8092 ****
       */
      *indexCorrelation = 0;

!     qinfos = deconstruct_indexquals(path);
!     foreach(l, qinfos)
      {
!         IndexQualInfo *qinfo = (IndexQualInfo *) lfirst(l);
!         AttrNumber    attnum = index->indexkeys[qinfo->indexcol];

          /* attempt to lookup stats in relation for this index column */
          if (attnum != 0)
--- 8093,8102 ----
       */
      *indexCorrelation = 0;

!     foreach(l, path->indexclauses)
      {
!         IndexClause *iclause = lfirst_node(IndexClause, l);
!         AttrNumber    attnum = index->indexkeys[iclause->indexcol];

          /* attempt to lookup stats in relation for this index column */
          if (attnum != 0)
*************** brincostestimate(PlannerInfo *root, Inde
*** 8121,8127 ****
               */

              /* get the attnum from the 0-based index. */
!             attnum = qinfo->indexcol + 1;

              if (get_index_stats_hook &&
                  (*get_index_stats_hook) (root, index->indexoid, attnum, &vardata))
--- 8131,8137 ----
               */

              /* get the attnum from the 0-based index. */
!             attnum = iclause->indexcol + 1;

              if (get_index_stats_hook &&
                  (*get_index_stats_hook) (root, index->indexoid, attnum, &vardata))
*************** brincostestimate(PlannerInfo *root, Inde
*** 8200,8209 ****

      /*
       * Compute the index qual costs, much as in genericcostestimate, to add to
!      * the index costs.
       */
!     qual_arg_cost = other_operands_eval_cost(root, qinfos) +
!         orderby_operands_eval_cost(root, path);

      /*
       * Compute the startup cost as the cost to read the whole revmap
--- 8210,8219 ----

      /*
       * Compute the index qual costs, much as in genericcostestimate, to add to
!      * the index costs.  We can disregard indexorderbys, since BRIN doesn't
!      * support those.
       */
!     qual_arg_cost = index_other_operands_eval_cost(root, indexQuals);

      /*
       * Compute the startup cost as the cost to read the whole revmap
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
index 087b56f..a617884 100644
*** a/src/include/utils/selfuncs.h
--- b/src/include/utils/selfuncs.h
*************** extern void estimate_hash_bucket_stats(P
*** 213,222 ****
                             Selectivity *mcv_freq,
                             Selectivity *bucketsize_frac);

  extern List *deconstruct_indexquals(IndexPath *path);
  extern void genericcostestimate(PlannerInfo *root, IndexPath *path,
                      double loop_count,
-                     List *qinfos,
                      GenericCosts *costs);

  /* Functions in array_selfuncs.c */
--- 213,224 ----
                             Selectivity *mcv_freq,
                             Selectivity *bucketsize_frac);

+ extern List *get_quals_from_indexclauses(List *indexclauses);
  extern List *deconstruct_indexquals(IndexPath *path);
+ extern Cost index_other_operands_eval_cost(PlannerInfo *root, List *indexquals);
+ extern List *add_predicate_to_index_quals(IndexOptInfo *index, List *indexQuals);
  extern void genericcostestimate(PlannerInfo *root, IndexPath *path,
                      double loop_count,
                      GenericCosts *costs);

  /* Functions in array_selfuncs.c */