summaryrefslogtreecommitdiffstats
path: root/src/backend/optimizer/util/plancat.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/optimizer/util/plancat.c')
-rw-r--r--src/backend/optimizer/util/plancat.c2410
1 files changed, 2410 insertions, 0 deletions
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
new file mode 100644
index 0000000..c5194fd
--- /dev/null
+++ b/src/backend/optimizer/util/plancat.c
@@ -0,0 +1,2410 @@
+/*-------------------------------------------------------------------------
+ *
+ * plancat.c
+ * routines for accessing the system catalogs
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/util/plancat.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <math.h>
+
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "access/sysattr.h"
+#include "access/table.h"
+#include "access/tableam.h"
+#include "access/transam.h"
+#include "access/xlog.h"
+#include "catalog/catalog.h"
+#include "catalog/heap.h"
+#include "catalog/pg_am.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_statistic_ext.h"
+#include "foreign/fdwapi.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/supportnodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/cost.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/plancat.h"
+#include "optimizer/prep.h"
+#include "parser/parse_relation.h"
+#include "parser/parsetree.h"
+#include "partitioning/partdesc.h"
+#include "rewrite/rewriteManip.h"
+#include "statistics/statistics.h"
+#include "storage/bufmgr.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/partcache.h"
+#include "utils/rel.h"
+#include "utils/snapmgr.h"
+#include "utils/syscache.h"
+
+/* GUC parameter */
+int constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION;
+
+/* Hook for plugins to get control in get_relation_info() */
+get_relation_info_hook_type get_relation_info_hook = NULL;
+
+
+static void get_relation_foreign_keys(PlannerInfo *root, RelOptInfo *rel,
+ Relation relation, bool inhparent);
+static bool infer_collation_opclass_match(InferenceElem *elem, Relation idxRel,
+ List *idxExprs);
+static List *get_relation_constraints(PlannerInfo *root,
+ Oid relationObjectId, RelOptInfo *rel,
+ bool include_noinherit,
+ bool include_notnull,
+ bool include_partition);
+static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
+ Relation heapRelation);
+static List *get_relation_statistics(RelOptInfo *rel, Relation relation);
+static void set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel,
+ Relation relation);
+static PartitionScheme find_partition_scheme(PlannerInfo *root, Relation rel);
+static void set_baserel_partition_key_exprs(Relation relation,
+ RelOptInfo *rel);
+static void set_baserel_partition_constraint(Relation relation,
+ RelOptInfo *rel);
+
+
+/*
+ * get_relation_info -
+ * Retrieves catalog information for a given relation.
+ *
+ * Given the Oid of the relation, return the following info into fields
+ * of the RelOptInfo struct:
+ *
+ * min_attr lowest valid AttrNumber
+ * max_attr highest valid AttrNumber
+ * indexlist list of IndexOptInfos for relation's indexes
+ * statlist list of StatisticExtInfo for relation's statistic objects
+ * serverid if it's a foreign table, the server OID
+ * fdwroutine if it's a foreign table, the FDW function pointers
+ * pages number of pages
+ * tuples number of tuples
+ * rel_parallel_workers user-defined number of parallel workers
+ *
+ * Also, add information about the relation's foreign keys to root->fkey_list.
+ *
+ * Also, initialize the attr_needed[] and attr_widths[] arrays. In most
+ * cases these are left as zeroes, but sometimes we need to compute attr
+ * widths here, and we may as well cache the results for costsize.c.
+ *
+ * If inhparent is true, all we need to do is set up the attr arrays:
+ * the RelOptInfo actually represents the appendrel formed by an inheritance
+ * tree, and so the parent rel's physical size and index information isn't
+ * important for it.
+ */
+void
+get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
+ RelOptInfo *rel)
+{
+ Index varno = rel->relid;
+ Relation relation;
+ bool hasindex;
+ List *indexinfos = NIL;
+
+ /*
+ * We need not lock the relation since it was already locked, either by
+ * the rewriter or when expand_inherited_rtentry() added it to the query's
+ * rangetable.
+ */
+ relation = table_open(relationObjectId, NoLock);
+
+ /* Temporary and unlogged relations are inaccessible during recovery. */
+ if (!RelationIsPermanent(relation) && RecoveryInProgress())
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot access temporary or unlogged relations during recovery")));
+
+ rel->min_attr = FirstLowInvalidHeapAttributeNumber + 1;
+ rel->max_attr = RelationGetNumberOfAttributes(relation);
+ rel->reltablespace = RelationGetForm(relation)->reltablespace;
+
+ Assert(rel->max_attr >= rel->min_attr);
+ rel->attr_needed = (Relids *)
+ palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(Relids));
+ rel->attr_widths = (int32 *)
+ palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(int32));
+
+ /*
+ * Estimate relation size --- unless it's an inheritance parent, in which
+ * case the size we want is not the rel's own size but the size of its
+ * inheritance tree. That will be computed in set_append_rel_size().
+ */
+ if (!inhparent)
+ estimate_rel_size(relation, rel->attr_widths - rel->min_attr,
+ &rel->pages, &rel->tuples, &rel->allvisfrac);
+
+ /* Retrieve the parallel_workers reloption, or -1 if not set. */
+ rel->rel_parallel_workers = RelationGetParallelWorkers(relation, -1);
+
+ /*
+ * Make list of indexes. Ignore indexes on system catalogs if told to.
+ * Don't bother with indexes for an inheritance parent, either.
+ */
+ if (inhparent ||
+ (IgnoreSystemIndexes && IsSystemRelation(relation)))
+ hasindex = false;
+ else
+ hasindex = relation->rd_rel->relhasindex;
+
+ if (hasindex)
+ {
+ List *indexoidlist;
+ LOCKMODE lmode;
+ ListCell *l;
+
+ indexoidlist = RelationGetIndexList(relation);
+
+ /*
+ * For each index, we get the same type of lock that the executor will
+ * need, and do not release it. This saves a couple of trips to the
+ * shared lock manager while not creating any real loss of
+ * concurrency, because no schema changes could be happening on the
+ * index while we hold lock on the parent rel, and no lock type used
+ * for queries blocks any other kind of index operation.
+ */
+ lmode = root->simple_rte_array[varno]->rellockmode;
+
+ foreach(l, indexoidlist)
+ {
+ Oid indexoid = lfirst_oid(l);
+ Relation indexRelation;
+ Form_pg_index index;
+ IndexAmRoutine *amroutine;
+ IndexOptInfo *info;
+ int ncolumns,
+ nkeycolumns;
+ int i;
+
+ /*
+ * Extract info from the relation descriptor for the index.
+ */
+ indexRelation = index_open(indexoid, lmode);
+ index = indexRelation->rd_index;
+
+ /*
+ * Ignore invalid indexes, since they can't safely be used for
+ * queries. Note that this is OK because the data structure we
+ * are constructing is only used by the planner --- the executor
+ * still needs to insert into "invalid" indexes, if they're marked
+ * indisready.
+ */
+ if (!index->indisvalid)
+ {
+ index_close(indexRelation, NoLock);
+ continue;
+ }
+
+ /*
+ * Ignore partitioned indexes, since they are not usable for
+ * queries.
+ */
+ if (indexRelation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
+ {
+ index_close(indexRelation, NoLock);
+ continue;
+ }
+
+ /*
+ * If the index is valid, but cannot yet be used, ignore it; but
+ * mark the plan we are generating as transient. See
+ * src/backend/access/heap/README.HOT for discussion.
+ */
+ if (index->indcheckxmin &&
+ !TransactionIdPrecedes(HeapTupleHeaderGetXmin(indexRelation->rd_indextuple->t_data),
+ TransactionXmin))
+ {
+ root->glob->transientPlan = true;
+ index_close(indexRelation, NoLock);
+ continue;
+ }
+
+ info = makeNode(IndexOptInfo);
+
+ info->indexoid = index->indexrelid;
+ info->reltablespace =
+ RelationGetForm(indexRelation)->reltablespace;
+ info->rel = rel;
+ info->ncolumns = ncolumns = index->indnatts;
+ info->nkeycolumns = nkeycolumns = index->indnkeyatts;
+
+ info->indexkeys = (int *) palloc(sizeof(int) * ncolumns);
+ info->indexcollations = (Oid *) palloc(sizeof(Oid) * nkeycolumns);
+ info->opfamily = (Oid *) palloc(sizeof(Oid) * nkeycolumns);
+ info->opcintype = (Oid *) palloc(sizeof(Oid) * nkeycolumns);
+ info->canreturn = (bool *) palloc(sizeof(bool) * ncolumns);
+
+ for (i = 0; i < ncolumns; i++)
+ {
+ info->indexkeys[i] = index->indkey.values[i];
+ info->canreturn[i] = index_can_return(indexRelation, i + 1);
+ }
+
+ for (i = 0; i < nkeycolumns; i++)
+ {
+ info->opfamily[i] = indexRelation->rd_opfamily[i];
+ info->opcintype[i] = indexRelation->rd_opcintype[i];
+ info->indexcollations[i] = indexRelation->rd_indcollation[i];
+ }
+
+ info->relam = indexRelation->rd_rel->relam;
+
+ /* We copy just the fields we need, not all of rd_indam */
+ amroutine = indexRelation->rd_indam;
+ info->amcanorderbyop = amroutine->amcanorderbyop;
+ info->amoptionalkey = amroutine->amoptionalkey;
+ info->amsearcharray = amroutine->amsearcharray;
+ info->amsearchnulls = amroutine->amsearchnulls;
+ info->amcanparallel = amroutine->amcanparallel;
+ info->amhasgettuple = (amroutine->amgettuple != NULL);
+ info->amhasgetbitmap = amroutine->amgetbitmap != NULL &&
+ relation->rd_tableam->scan_bitmap_next_block != NULL;
+ info->amcanmarkpos = (amroutine->ammarkpos != NULL &&
+ amroutine->amrestrpos != NULL);
+ info->amcostestimate = amroutine->amcostestimate;
+ Assert(info->amcostestimate != NULL);
+
+ /* Fetch index opclass options */
+ info->opclassoptions = RelationGetIndexAttOptions(indexRelation, true);
+
+ /*
+ * Fetch the ordering information for the index, if any.
+ */
+ if (info->relam == BTREE_AM_OID)
+ {
+ /*
+ * If it's a btree index, we can use its opfamily OIDs
+ * directly as the sort ordering opfamily OIDs.
+ */
+ Assert(amroutine->amcanorder);
+
+ info->sortopfamily = info->opfamily;
+ info->reverse_sort = (bool *) palloc(sizeof(bool) * nkeycolumns);
+ info->nulls_first = (bool *) palloc(sizeof(bool) * nkeycolumns);
+
+ for (i = 0; i < nkeycolumns; i++)
+ {
+ int16 opt = indexRelation->rd_indoption[i];
+
+ info->reverse_sort[i] = (opt & INDOPTION_DESC) != 0;
+ info->nulls_first[i] = (opt & INDOPTION_NULLS_FIRST) != 0;
+ }
+ }
+ else if (amroutine->amcanorder)
+ {
+ /*
+ * Otherwise, identify the corresponding btree opfamilies by
+ * trying to map this index's "<" operators into btree. Since
+ * "<" uniquely defines the behavior of a sort order, this is
+ * a sufficient test.
+ *
+ * XXX This method is rather slow and also requires the
+ * undesirable assumption that the other index AM numbers its
+ * strategies the same as btree. It'd be better to have a way
+ * to explicitly declare the corresponding btree opfamily for
+ * each opfamily of the other index type. But given the lack
+ * of current or foreseeable amcanorder index types, it's not
+ * worth expending more effort on now.
+ */
+ info->sortopfamily = (Oid *) palloc(sizeof(Oid) * nkeycolumns);
+ info->reverse_sort = (bool *) palloc(sizeof(bool) * nkeycolumns);
+ info->nulls_first = (bool *) palloc(sizeof(bool) * nkeycolumns);
+
+ for (i = 0; i < nkeycolumns; i++)
+ {
+ int16 opt = indexRelation->rd_indoption[i];
+ Oid ltopr;
+ Oid btopfamily;
+ Oid btopcintype;
+ int16 btstrategy;
+
+ info->reverse_sort[i] = (opt & INDOPTION_DESC) != 0;
+ info->nulls_first[i] = (opt & INDOPTION_NULLS_FIRST) != 0;
+
+ ltopr = get_opfamily_member(info->opfamily[i],
+ info->opcintype[i],
+ info->opcintype[i],
+ BTLessStrategyNumber);
+ if (OidIsValid(ltopr) &&
+ get_ordering_op_properties(ltopr,
+ &btopfamily,
+ &btopcintype,
+ &btstrategy) &&
+ btopcintype == info->opcintype[i] &&
+ btstrategy == BTLessStrategyNumber)
+ {
+ /* Successful mapping */
+ info->sortopfamily[i] = btopfamily;
+ }
+ else
+ {
+ /* Fail ... quietly treat index as unordered */
+ info->sortopfamily = NULL;
+ info->reverse_sort = NULL;
+ info->nulls_first = NULL;
+ break;
+ }
+ }
+ }
+ else
+ {
+ info->sortopfamily = NULL;
+ info->reverse_sort = NULL;
+ info->nulls_first = NULL;
+ }
+
+ /*
+ * Fetch the index expressions and predicate, if any. We must
+ * modify the copies we obtain from the relcache to have the
+ * correct varno for the parent relation, so that they match up
+ * correctly against qual clauses.
+ */
+ info->indexprs = RelationGetIndexExpressions(indexRelation);
+ info->indpred = RelationGetIndexPredicate(indexRelation);
+ if (info->indexprs && varno != 1)
+ ChangeVarNodes((Node *) info->indexprs, 1, varno, 0);
+ if (info->indpred && varno != 1)
+ ChangeVarNodes((Node *) info->indpred, 1, varno, 0);
+
+ /* Build targetlist using the completed indexprs data */
+ info->indextlist = build_index_tlist(root, info, relation);
+
+ info->indrestrictinfo = NIL; /* set later, in indxpath.c */
+ info->predOK = false; /* set later, in indxpath.c */
+ info->unique = index->indisunique;
+ info->immediate = index->indimmediate;
+ info->hypothetical = false;
+
+ /*
+ * Estimate the index size. If it's not a partial index, we lock
+ * the number-of-tuples estimate to equal the parent table; if it
+ * is partial then we have to use the same methods as we would for
+ * a table, except we can be sure that the index is not larger
+ * than the table.
+ */
+ if (info->indpred == NIL)
+ {
+ info->pages = RelationGetNumberOfBlocks(indexRelation);
+ info->tuples = rel->tuples;
+ }
+ else
+ {
+ double allvisfrac; /* dummy */
+
+ estimate_rel_size(indexRelation, NULL,
+ &info->pages, &info->tuples, &allvisfrac);
+ if (info->tuples > rel->tuples)
+ info->tuples = rel->tuples;
+ }
+
+ if (info->relam == BTREE_AM_OID)
+ {
+ /* For btrees, get tree height while we have the index open */
+ info->tree_height = _bt_getrootheight(indexRelation);
+ }
+ else
+ {
+ /* For other index types, just set it to "unknown" for now */
+ info->tree_height = -1;
+ }
+
+ index_close(indexRelation, NoLock);
+
+ /*
+ * We've historically used lcons() here. It'd make more sense to
+ * use lappend(), but that causes the planner to change behavior
+ * in cases where two indexes seem equally attractive. For now,
+ * stick with lcons() --- few tables should have so many indexes
+ * that the O(N^2) behavior of lcons() is really a problem.
+ */
+ indexinfos = lcons(info, indexinfos);
+ }
+
+ list_free(indexoidlist);
+ }
+
+ rel->indexlist = indexinfos;
+
+ rel->statlist = get_relation_statistics(rel, relation);
+
+ /* Grab foreign-table info using the relcache, while we have it */
+ if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ rel->serverid = GetForeignServerIdByRelId(RelationGetRelid(relation));
+ rel->fdwroutine = GetFdwRoutineForRelation(relation, true);
+ }
+ else
+ {
+ rel->serverid = InvalidOid;
+ rel->fdwroutine = NULL;
+ }
+
+ /* Collect info about relation's foreign keys, if relevant */
+ get_relation_foreign_keys(root, rel, relation, inhparent);
+
+ /* Collect info about functions implemented by the rel's table AM. */
+ if (relation->rd_tableam &&
+ relation->rd_tableam->scan_set_tidrange != NULL &&
+ relation->rd_tableam->scan_getnextslot_tidrange != NULL)
+ rel->amflags |= AMFLAG_HAS_TID_RANGE;
+
+ /*
+ * Collect info about relation's partitioning scheme, if any. Only
+ * inheritance parents may be partitioned.
+ */
+ if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ set_relation_partition_info(root, rel, relation);
+
+ table_close(relation, NoLock);
+
+ /*
+ * Allow a plugin to editorialize on the info we obtained from the
+ * catalogs. Actions might include altering the assumed relation size,
+ * removing an index, or adding a hypothetical index to the indexlist.
+ */
+ if (get_relation_info_hook)
+ (*get_relation_info_hook) (root, relationObjectId, inhparent, rel);
+}
+
+/*
+ * get_relation_foreign_keys -
+ * Retrieves foreign key information for a given relation.
+ *
+ * ForeignKeyOptInfos for relevant foreign keys are created and added to
+ * root->fkey_list. We do this now while we have the relcache entry open.
+ * We could sometimes avoid making useless ForeignKeyOptInfos if we waited
+ * until all RelOptInfos have been built, but the cost of re-opening the
+ * relcache entries would probably exceed any savings.
+ */
+static void
+get_relation_foreign_keys(PlannerInfo *root, RelOptInfo *rel,
+ Relation relation, bool inhparent)
+{
+ List *rtable = root->parse->rtable;
+ List *cachedfkeys;
+ ListCell *lc;
+
+ /*
+ * If it's not a baserel, we don't care about its FKs. Also, if the query
+ * references only a single relation, we can skip the lookup since no FKs
+ * could satisfy the requirements below.
+ */
+ if (rel->reloptkind != RELOPT_BASEREL ||
+ list_length(rtable) < 2)
+ return;
+
+ /*
+ * If it's the parent of an inheritance tree, ignore its FKs. We could
+ * make useful FK-based deductions if we found that all members of the
+ * inheritance tree have equivalent FK constraints, but detecting that
+ * would require code that hasn't been written.
+ */
+ if (inhparent)
+ return;
+
+ /*
+ * Extract data about relation's FKs from the relcache. Note that this
+ * list belongs to the relcache and might disappear in a cache flush, so
+ * we must not do any further catalog access within this function.
+ */
+ cachedfkeys = RelationGetFKeyList(relation);
+
+ /*
+ * Figure out which FKs are of interest for this query, and create
+ * ForeignKeyOptInfos for them. We want only FKs that reference some
+ * other RTE of the current query. In queries containing self-joins,
+ * there might be more than one other RTE for a referenced table, and we
+ * should make a ForeignKeyOptInfo for each occurrence.
+ *
+ * Ideally, we would ignore RTEs that correspond to non-baserels, but it's
+ * too hard to identify those here, so we might end up making some useless
+ * ForeignKeyOptInfos. If so, match_foreign_keys_to_quals() will remove
+ * them again.
+ */
+ foreach(lc, cachedfkeys)
+ {
+ ForeignKeyCacheInfo *cachedfk = (ForeignKeyCacheInfo *) lfirst(lc);
+ Index rti;
+ ListCell *lc2;
+
+ /* conrelid should always be that of the table we're considering */
+ Assert(cachedfk->conrelid == RelationGetRelid(relation));
+
+ /* Scan to find other RTEs matching confrelid */
+ rti = 0;
+ foreach(lc2, rtable)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc2);
+ ForeignKeyOptInfo *info;
+
+ rti++;
+ /* Ignore if not the correct table */
+ if (rte->rtekind != RTE_RELATION ||
+ rte->relid != cachedfk->confrelid)
+ continue;
+ /* Ignore if it's an inheritance parent; doesn't really match */
+ if (rte->inh)
+ continue;
+ /* Ignore self-referential FKs; we only care about joins */
+ if (rti == rel->relid)
+ continue;
+
+ /* OK, let's make an entry */
+ info = makeNode(ForeignKeyOptInfo);
+ info->con_relid = rel->relid;
+ info->ref_relid = rti;
+ info->nkeys = cachedfk->nkeys;
+ memcpy(info->conkey, cachedfk->conkey, sizeof(info->conkey));
+ memcpy(info->confkey, cachedfk->confkey, sizeof(info->confkey));
+ memcpy(info->conpfeqop, cachedfk->conpfeqop, sizeof(info->conpfeqop));
+ /* zero out fields to be filled by match_foreign_keys_to_quals */
+ info->nmatched_ec = 0;
+ info->nconst_ec = 0;
+ info->nmatched_rcols = 0;
+ info->nmatched_ri = 0;
+ memset(info->eclass, 0, sizeof(info->eclass));
+ memset(info->fk_eclass_member, 0, sizeof(info->fk_eclass_member));
+ memset(info->rinfos, 0, sizeof(info->rinfos));
+
+ root->fkey_list = lappend(root->fkey_list, info);
+ }
+ }
+}
+
+/*
+ * infer_arbiter_indexes -
+ * Determine the unique indexes used to arbitrate speculative insertion.
+ *
+ * Uses user-supplied inference clause expressions and predicate to match a
+ * unique index from those defined and ready on the heap relation (target).
+ * An exact match is required on columns/expressions (although they can appear
+ * in any order). However, the predicate given by the user need only restrict
+ * insertion to a subset of some part of the table covered by some particular
+ * unique index (in particular, a partial unique index) in order to be
+ * inferred.
+ *
+ * The implementation does not consider which B-Tree operator class any
+ * particular available unique index attribute uses, unless one was specified
+ * in the inference specification. The same is true of collations. In
+ * particular, there is no system dependency on the default operator class for
+ * the purposes of inference. If no opclass (or collation) is specified, then
+ * all matching indexes (that may or may not match the default in terms of
+ * each attribute opclass/collation) are used for inference.
+ */
+List *
+infer_arbiter_indexes(PlannerInfo *root)
+{
+ OnConflictExpr *onconflict = root->parse->onConflict;
+
+ /* Iteration state */
+ RangeTblEntry *rte;
+ Relation relation;
+ Oid indexOidFromConstraint = InvalidOid;
+ List *indexList;
+ ListCell *l;
+
+ /* Normalized inference attributes and inference expressions: */
+ Bitmapset *inferAttrs = NULL;
+ List *inferElems = NIL;
+
+ /* Results */
+ List *results = NIL;
+
+ /*
+ * Quickly return NIL for ON CONFLICT DO NOTHING without an inference
+ * specification or named constraint. ON CONFLICT DO UPDATE statements
+ * must always provide one or the other (but parser ought to have caught
+ * that already).
+ */
+ if (onconflict->arbiterElems == NIL &&
+ onconflict->constraint == InvalidOid)
+ return NIL;
+
+ /*
+ * We need not lock the relation since it was already locked, either by
+ * the rewriter or when expand_inherited_rtentry() added it to the query's
+ * rangetable.
+ */
+ rte = rt_fetch(root->parse->resultRelation, root->parse->rtable);
+
+ relation = table_open(rte->relid, NoLock);
+
+ /*
+ * Build normalized/BMS representation of plain indexed attributes, as
+ * well as a separate list of expression items. This simplifies matching
+ * the cataloged definition of indexes.
+ */
+ foreach(l, onconflict->arbiterElems)
+ {
+ InferenceElem *elem = (InferenceElem *) lfirst(l);
+ Var *var;
+ int attno;
+
+ if (!IsA(elem->expr, Var))
+ {
+ /* If not a plain Var, just shove it in inferElems for now */
+ inferElems = lappend(inferElems, elem->expr);
+ continue;
+ }
+
+ var = (Var *) elem->expr;
+ attno = var->varattno;
+
+ if (attno == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("whole row unique index inference specifications are not supported")));
+
+ inferAttrs = bms_add_member(inferAttrs,
+ attno - FirstLowInvalidHeapAttributeNumber);
+ }
+
+ /*
+ * Lookup named constraint's index. This is not immediately returned
+ * because some additional sanity checks are required.
+ */
+ if (onconflict->constraint != InvalidOid)
+ {
+ indexOidFromConstraint = get_constraint_index(onconflict->constraint);
+
+ if (indexOidFromConstraint == InvalidOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("constraint in ON CONFLICT clause has no associated index")));
+ }
+
+ /*
+ * Using that representation, iterate through the list of indexes on the
+ * target relation to try and find a match
+ */
+ indexList = RelationGetIndexList(relation);
+
+ foreach(l, indexList)
+ {
+ Oid indexoid = lfirst_oid(l);
+ Relation idxRel;
+ Form_pg_index idxForm;
+ Bitmapset *indexedAttrs;
+ List *idxExprs;
+ List *predExprs;
+ AttrNumber natt;
+ ListCell *el;
+
+ /*
+ * Extract info from the relation descriptor for the index. Obtain
+ * the same lock type that the executor will ultimately use.
+ *
+ * Let executor complain about !indimmediate case directly, because
+ * enforcement needs to occur there anyway when an inference clause is
+ * omitted.
+ */
+ idxRel = index_open(indexoid, rte->rellockmode);
+ idxForm = idxRel->rd_index;
+
+ if (!idxForm->indisvalid)
+ goto next;
+
+ /*
+ * Note that we do not perform a check against indcheckxmin (like e.g.
+ * get_relation_info()) here to eliminate candidates, because
+ * uniqueness checking only cares about the most recently committed
+ * tuple versions.
+ */
+
+ /*
+ * Look for match on "ON constraint_name" variant, which may not be
+ * unique constraint. This can only be a constraint name.
+ */
+ if (indexOidFromConstraint == idxForm->indexrelid)
+ {
+ if (!idxForm->indisunique && onconflict->action == ONCONFLICT_UPDATE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("ON CONFLICT DO UPDATE not supported with exclusion constraints")));
+
+ results = lappend_oid(results, idxForm->indexrelid);
+ list_free(indexList);
+ index_close(idxRel, NoLock);
+ table_close(relation, NoLock);
+ return results;
+ }
+ else if (indexOidFromConstraint != InvalidOid)
+ {
+ /* No point in further work for index in named constraint case */
+ goto next;
+ }
+
+ /*
+ * Only considering conventional inference at this point (not named
+ * constraints), so index under consideration can be immediately
+ * skipped if it's not unique
+ */
+ if (!idxForm->indisunique)
+ goto next;
+
+ /* Build BMS representation of plain (non expression) index attrs */
+ indexedAttrs = NULL;
+ for (natt = 0; natt < idxForm->indnkeyatts; natt++)
+ {
+ int attno = idxRel->rd_index->indkey.values[natt];
+
+ if (attno != 0)
+ indexedAttrs = bms_add_member(indexedAttrs,
+ attno - FirstLowInvalidHeapAttributeNumber);
+ }
+
+ /* Non-expression attributes (if any) must match */
+ if (!bms_equal(indexedAttrs, inferAttrs))
+ goto next;
+
+ /* Expression attributes (if any) must match */
+ idxExprs = RelationGetIndexExpressions(idxRel);
+ foreach(el, onconflict->arbiterElems)
+ {
+ InferenceElem *elem = (InferenceElem *) lfirst(el);
+
+ /*
+ * Ensure that collation/opclass aspects of inference expression
+ * element match. Even though this loop is primarily concerned
+ * with matching expressions, it is a convenient point to check
+ * this for both expressions and ordinary (non-expression)
+ * attributes appearing as inference elements.
+ */
+ if (!infer_collation_opclass_match(elem, idxRel, idxExprs))
+ goto next;
+
+ /*
+ * Plain Vars don't factor into count of expression elements, and
+ * the question of whether or not they satisfy the index
+ * definition has already been considered (they must).
+ */
+ if (IsA(elem->expr, Var))
+ continue;
+
+ /*
+ * Might as well avoid redundant check in the rare cases where
+ * infer_collation_opclass_match() is required to do real work.
+ * Otherwise, check that element expression appears in cataloged
+ * index definition.
+ */
+ if (elem->infercollid != InvalidOid ||
+ elem->inferopclass != InvalidOid ||
+ list_member(idxExprs, elem->expr))
+ continue;
+
+ goto next;
+ }
+
+ /*
+ * Now that all inference elements were matched, ensure that the
+ * expression elements from inference clause are not missing any
+ * cataloged expressions. This does the right thing when unique
+ * indexes redundantly repeat the same attribute, or if attributes
+ * redundantly appear multiple times within an inference clause.
+ */
+ if (list_difference(idxExprs, inferElems) != NIL)
+ goto next;
+
+ /*
+ * If it's a partial index, its predicate must be implied by the ON
+ * CONFLICT's WHERE clause.
+ */
+ predExprs = RelationGetIndexPredicate(idxRel);
+
+ if (!predicate_implied_by(predExprs, (List *) onconflict->arbiterWhere, false))
+ goto next;
+
+ results = lappend_oid(results, idxForm->indexrelid);
+next:
+ index_close(idxRel, NoLock);
+ }
+
+ list_free(indexList);
+ table_close(relation, NoLock);
+
+ if (results == NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+ errmsg("there is no unique or exclusion constraint matching the ON CONFLICT specification")));
+
+ return results;
+}
+
+/*
+ * infer_collation_opclass_match - ensure infer element opclass/collation match
+ *
+ * Given unique index inference element from inference specification, if
+ * collation was specified, or if opclass was specified, verify that there is
+ * at least one matching indexed attribute (occasionally, there may be more).
+ * Skip this in the common case where inference specification does not include
+ * collation or opclass (instead matching everything, regardless of cataloged
+ * collation/opclass of indexed attribute).
+ *
+ * At least historically, Postgres has not offered collations or opclasses
+ * with alternative-to-default notions of equality, so these additional
+ * criteria should only be required infrequently.
+ *
+ * Don't give up immediately when an inference element matches some attribute
+ * cataloged as indexed but not matching additional opclass/collation
+ * criteria. This is done so that the implementation is as forgiving as
+ * possible of redundancy within cataloged index attributes (or, less
+ * usefully, within inference specification elements). If collations actually
+ * differ between apparently redundantly indexed attributes (redundant within
+ * or across indexes), then there really is no redundancy as such.
+ *
+ * Note that if an inference element specifies an opclass and a collation at
+ * once, both must match in at least one particular attribute within index
+ * catalog definition in order for that inference element to be considered
+ * inferred/satisfied.
+ */
+static bool
+infer_collation_opclass_match(InferenceElem *elem, Relation idxRel,
+ List *idxExprs)
+{
+ AttrNumber natt;
+ Oid inferopfamily = InvalidOid; /* OID of opclass opfamily */
+ Oid inferopcinputtype = InvalidOid; /* OID of opclass input type */
+ int nplain = 0; /* # plain attrs observed */
+
+ /*
+ * If inference specification element lacks collation/opclass, then no
+ * need to check for exact match.
+ */
+ if (elem->infercollid == InvalidOid && elem->inferopclass == InvalidOid)
+ return true;
+
+ /*
+ * Lookup opfamily and input type, for matching indexes
+ */
+ if (elem->inferopclass)
+ {
+ inferopfamily = get_opclass_family(elem->inferopclass);
+ inferopcinputtype = get_opclass_input_type(elem->inferopclass);
+ }
+
+ for (natt = 1; natt <= idxRel->rd_att->natts; natt++)
+ {
+ Oid opfamily = idxRel->rd_opfamily[natt - 1];
+ Oid opcinputtype = idxRel->rd_opcintype[natt - 1];
+ Oid collation = idxRel->rd_indcollation[natt - 1];
+ int attno = idxRel->rd_index->indkey.values[natt - 1];
+
+ if (attno != 0)
+ nplain++;
+
+ if (elem->inferopclass != InvalidOid &&
+ (inferopfamily != opfamily || inferopcinputtype != opcinputtype))
+ {
+ /* Attribute needed to match opclass, but didn't */
+ continue;
+ }
+
+ if (elem->infercollid != InvalidOid &&
+ elem->infercollid != collation)
+ {
+ /* Attribute needed to match collation, but didn't */
+ continue;
+ }
+
+ /* If one matching index att found, good enough -- return true */
+ if (IsA(elem->expr, Var))
+ {
+ if (((Var *) elem->expr)->varattno == attno)
+ return true;
+ }
+ else if (attno == 0)
+ {
+ Node *nattExpr = list_nth(idxExprs, (natt - 1) - nplain);
+
+ /*
+ * Note that unlike routines like match_index_to_operand() we
+ * don't need to care about RelabelType. Neither the index
+ * definition nor the inference clause should contain them.
+ */
+ if (equal(elem->expr, nattExpr))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * estimate_rel_size - estimate # pages and # tuples in a table or index
+ *
+ * We also estimate the fraction of the pages that are marked all-visible in
+ * the visibility map, for use in estimation of index-only scans.
+ *
+ * If attr_widths isn't NULL, it points to the zero-index entry of the
+ * relation's attr_widths[] cache; we fill this in if we have need to compute
+ * the attribute widths for estimation purposes.
+ */
+void
+estimate_rel_size(Relation rel, int32 *attr_widths,
+ BlockNumber *pages, double *tuples, double *allvisfrac)
+{
+ BlockNumber curpages;
+ BlockNumber relpages;
+ double reltuples;
+ BlockNumber relallvisible;
+ double density;
+
+ switch (rel->rd_rel->relkind)
+ {
+ case RELKIND_RELATION:
+ case RELKIND_MATVIEW:
+ case RELKIND_TOASTVALUE:
+ table_relation_estimate_size(rel, attr_widths, pages, tuples,
+ allvisfrac);
+ break;
+
+ case RELKIND_INDEX:
+
+ /*
+ * XXX: It'd probably be good to move this into a callback,
+ * individual index types e.g. know if they have a metapage.
+ */
+
+ /* it has storage, ok to call the smgr */
+ curpages = RelationGetNumberOfBlocks(rel);
+
+ /* report estimated # pages */
+ *pages = curpages;
+ /* quick exit if rel is clearly empty */
+ if (curpages == 0)
+ {
+ *tuples = 0;
+ *allvisfrac = 0;
+ break;
+ }
+
+ /* coerce values in pg_class to more desirable types */
+ relpages = (BlockNumber) rel->rd_rel->relpages;
+ reltuples = (double) rel->rd_rel->reltuples;
+ relallvisible = (BlockNumber) rel->rd_rel->relallvisible;
+
+ /*
+ * Discount the metapage while estimating the number of tuples.
+ * This is a kluge because it assumes more than it ought to about
+ * index structure. Currently it's OK for btree, hash, and GIN
+ * indexes but suspect for GiST indexes.
+ */
+ if (relpages > 0)
+ {
+ curpages--;
+ relpages--;
+ }
+
+ /* estimate number of tuples from previous tuple density */
+ if (reltuples >= 0 && relpages > 0)
+ density = reltuples / (double) relpages;
+ else
+ {
+ /*
+ * If we have no data because the relation was never vacuumed,
+ * estimate tuple width from attribute datatypes. We assume
+ * here that the pages are completely full, which is OK for
+ * tables (since they've presumably not been VACUUMed yet) but
+ * is probably an overestimate for indexes. Fortunately
+ * get_relation_info() can clamp the overestimate to the
+ * parent table's size.
+ *
+ * Note: this code intentionally disregards alignment
+ * considerations, because (a) that would be gilding the lily
+ * considering how crude the estimate is, and (b) it creates
+ * platform dependencies in the default plans which are kind
+ * of a headache for regression testing.
+ *
+ * XXX: Should this logic be more index specific?
+ */
+ int32 tuple_width;
+
+ tuple_width = get_rel_data_width(rel, attr_widths);
+ tuple_width += MAXALIGN(SizeofHeapTupleHeader);
+ tuple_width += sizeof(ItemIdData);
+ /* note: integer division is intentional here */
+ density = (BLCKSZ - SizeOfPageHeaderData) / tuple_width;
+ }
+ *tuples = rint(density * (double) curpages);
+
+ /*
+ * We use relallvisible as-is, rather than scaling it up like we
+ * do for the pages and tuples counts, on the theory that any
+ * pages added since the last VACUUM are most likely not marked
+ * all-visible. But costsize.c wants it converted to a fraction.
+ */
+ if (relallvisible == 0 || curpages <= 0)
+ *allvisfrac = 0;
+ else if ((double) relallvisible >= curpages)
+ *allvisfrac = 1;
+ else
+ *allvisfrac = (double) relallvisible / curpages;
+ break;
+
+ case RELKIND_SEQUENCE:
+ /* Sequences always have a known size */
+ *pages = 1;
+ *tuples = 1;
+ *allvisfrac = 0;
+ break;
+ case RELKIND_FOREIGN_TABLE:
+ /* Just use whatever's in pg_class */
+ /* Note that FDW must cope if reltuples is -1! */
+ *pages = rel->rd_rel->relpages;
+ *tuples = rel->rd_rel->reltuples;
+ *allvisfrac = 0;
+ break;
+ default:
+ /* else it has no disk storage; probably shouldn't get here? */
+ *pages = 0;
+ *tuples = 0;
+ *allvisfrac = 0;
+ break;
+ }
+}
+
+
+/*
+ * get_rel_data_width
+ *
+ * Estimate the average width of (the data part of) the relation's tuples.
+ *
+ * If attr_widths isn't NULL, it points to the zero-index entry of the
+ * relation's attr_widths[] cache; use and update that cache as appropriate.
+ *
+ * Currently we ignore dropped columns. Ideally those should be included
+ * in the result, but we haven't got any way to get info about them; and
+ * since they might be mostly NULLs, treating them as zero-width is not
+ * necessarily the wrong thing anyway.
+ */
+int32
+get_rel_data_width(Relation rel, int32 *attr_widths)
+{
+ int32 tuple_width = 0;
+ int i;
+
+ for (i = 1; i <= RelationGetNumberOfAttributes(rel); i++)
+ {
+ Form_pg_attribute att = TupleDescAttr(rel->rd_att, i - 1);
+ int32 item_width;
+
+ if (att->attisdropped)
+ continue;
+
+ /* use previously cached data, if any */
+ if (attr_widths != NULL && attr_widths[i] > 0)
+ {
+ tuple_width += attr_widths[i];
+ continue;
+ }
+
+ /* This should match set_rel_width() in costsize.c */
+ item_width = get_attavgwidth(RelationGetRelid(rel), i);
+ if (item_width <= 0)
+ {
+ item_width = get_typavgwidth(att->atttypid, att->atttypmod);
+ Assert(item_width > 0);
+ }
+ if (attr_widths != NULL)
+ attr_widths[i] = item_width;
+ tuple_width += item_width;
+ }
+
+ return tuple_width;
+}
+
+/*
+ * get_relation_data_width
+ *
+ * External API for get_rel_data_width: same behavior except we have to
+ * open the relcache entry.
+ */
+int32
+get_relation_data_width(Oid relid, int32 *attr_widths)
+{
+ int32 result;
+ Relation relation;
+
+ /* As above, assume relation is already locked */
+ relation = table_open(relid, NoLock);
+
+ result = get_rel_data_width(relation, attr_widths);
+
+ table_close(relation, NoLock);
+
+ return result;
+}
+
+
+/*
+ * get_relation_constraints
+ *
+ * Retrieve the applicable constraint expressions of the given relation.
+ *
+ * Returns a List (possibly empty) of constraint expressions. Each one
+ * has been canonicalized, and its Vars are changed to have the varno
+ * indicated by rel->relid. This allows the expressions to be easily
+ * compared to expressions taken from WHERE.
+ *
+ * If include_noinherit is true, it's okay to include constraints that
+ * are marked NO INHERIT.
+ *
+ * If include_notnull is true, "col IS NOT NULL" expressions are generated
+ * and added to the result for each column that's marked attnotnull.
+ *
+ * If include_partition is true, and the relation is a partition,
+ * also include the partitioning constraints.
+ *
+ * Note: at present this is invoked at most once per relation per planner
+ * run, and in many cases it won't be invoked at all, so there seems no
+ * point in caching the data in RelOptInfo.
+ */
+static List *
+get_relation_constraints(PlannerInfo *root,
+ Oid relationObjectId, RelOptInfo *rel,
+ bool include_noinherit,
+ bool include_notnull,
+ bool include_partition)
+{
+ List *result = NIL;
+ Index varno = rel->relid;
+ Relation relation;
+ TupleConstr *constr;
+
+ /*
+ * We assume the relation has already been safely locked.
+ */
+ relation = table_open(relationObjectId, NoLock);
+
+ constr = relation->rd_att->constr;
+ if (constr != NULL)
+ {
+ int num_check = constr->num_check;
+ int i;
+
+ for (i = 0; i < num_check; i++)
+ {
+ Node *cexpr;
+
+ /*
+ * If this constraint hasn't been fully validated yet, we must
+ * ignore it here. Also ignore if NO INHERIT and we weren't told
+ * that that's safe.
+ */
+ if (!constr->check[i].ccvalid)
+ continue;
+ if (constr->check[i].ccnoinherit && !include_noinherit)
+ continue;
+
+ cexpr = stringToNode(constr->check[i].ccbin);
+
+ /*
+ * Run each expression through const-simplification and
+ * canonicalization. This is not just an optimization, but is
+ * necessary, because we will be comparing it to
+ * similarly-processed qual clauses, and may fail to detect valid
+ * matches without this. This must match the processing done to
+ * qual clauses in preprocess_expression()! (We can skip the
+ * stuff involving subqueries, however, since we don't allow any
+ * in check constraints.)
+ */
+ cexpr = eval_const_expressions(root, cexpr);
+
+ cexpr = (Node *) canonicalize_qual((Expr *) cexpr, true);
+
+ /* Fix Vars to have the desired varno */
+ if (varno != 1)
+ ChangeVarNodes(cexpr, 1, varno, 0);
+
+ /*
+ * Finally, convert to implicit-AND format (that is, a List) and
+ * append the resulting item(s) to our output list.
+ */
+ result = list_concat(result,
+ make_ands_implicit((Expr *) cexpr));
+ }
+
+ /* Add NOT NULL constraints in expression form, if requested */
+ if (include_notnull && constr->has_not_null)
+ {
+ int natts = relation->rd_att->natts;
+
+ for (i = 1; i <= natts; i++)
+ {
+ Form_pg_attribute att = TupleDescAttr(relation->rd_att, i - 1);
+
+ if (att->attnotnull && !att->attisdropped)
+ {
+ NullTest *ntest = makeNode(NullTest);
+
+ ntest->arg = (Expr *) makeVar(varno,
+ i,
+ att->atttypid,
+ att->atttypmod,
+ att->attcollation,
+ 0);
+ ntest->nulltesttype = IS_NOT_NULL;
+
+ /*
+ * argisrow=false is correct even for a composite column,
+ * because attnotnull does not represent a SQL-spec IS NOT
+ * NULL test in such a case, just IS DISTINCT FROM NULL.
+ */
+ ntest->argisrow = false;
+ ntest->location = -1;
+ result = lappend(result, ntest);
+ }
+ }
+ }
+ }
+
+ /*
+ * Add partitioning constraints, if requested.
+ */
+ if (include_partition && relation->rd_rel->relispartition)
+ {
+ /* make sure rel->partition_qual is set */
+ set_baserel_partition_constraint(relation, rel);
+ result = list_concat(result, rel->partition_qual);
+ }
+
+ table_close(relation, NoLock);
+
+ return result;
+}
+
+/*
+ * get_relation_statistics
+ * Retrieve extended statistics defined on the table.
+ *
+ * Returns a List (possibly empty) of StatisticExtInfo objects describing
+ * the statistics. Note that this doesn't load the actual statistics data,
+ * just the identifying metadata. Only stats actually built are considered.
+ */
+static List *
+get_relation_statistics(RelOptInfo *rel, Relation relation)
+{
+ Index varno = rel->relid;
+ List *statoidlist;
+ List *stainfos = NIL;
+ ListCell *l;
+
+ statoidlist = RelationGetStatExtList(relation);
+
+ foreach(l, statoidlist)
+ {
+ Oid statOid = lfirst_oid(l);
+ Form_pg_statistic_ext staForm;
+ HeapTuple htup;
+ HeapTuple dtup;
+ Bitmapset *keys = NULL;
+ List *exprs = NIL;
+ int i;
+
+ htup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statOid));
+ if (!HeapTupleIsValid(htup))
+ elog(ERROR, "cache lookup failed for statistics object %u", statOid);
+ staForm = (Form_pg_statistic_ext) GETSTRUCT(htup);
+
+ dtup = SearchSysCache1(STATEXTDATASTXOID, ObjectIdGetDatum(statOid));
+ if (!HeapTupleIsValid(dtup))
+ elog(ERROR, "cache lookup failed for statistics object %u", statOid);
+
+ /*
+ * First, build the array of columns covered. This is ultimately
+ * wasted if no stats within the object have actually been built, but
+ * it doesn't seem worth troubling over that case.
+ */
+ for (i = 0; i < staForm->stxkeys.dim1; i++)
+ keys = bms_add_member(keys, staForm->stxkeys.values[i]);
+
+ /*
+ * Preprocess expressions (if any). We read the expressions, run them
+ * through eval_const_expressions, and fix the varnos.
+ */
+ {
+ bool isnull;
+ Datum datum;
+
+ /* decode expression (if any) */
+ datum = SysCacheGetAttr(STATEXTOID, htup,
+ Anum_pg_statistic_ext_stxexprs, &isnull);
+
+ if (!isnull)
+ {
+ char *exprsString;
+
+ exprsString = TextDatumGetCString(datum);
+ exprs = (List *) stringToNode(exprsString);
+ pfree(exprsString);
+
+ /*
+ * Run the expressions through eval_const_expressions. This is
+ * not just an optimization, but is necessary, because the
+ * planner will be comparing them to similarly-processed qual
+ * clauses, and may fail to detect valid matches without this.
+ * We must not use canonicalize_qual, however, since these
+ * aren't qual expressions.
+ */
+ exprs = (List *) eval_const_expressions(NULL, (Node *) exprs);
+
+ /* May as well fix opfuncids too */
+ fix_opfuncids((Node *) exprs);
+
+ /*
+ * Modify the copies we obtain from the relcache to have the
+ * correct varno for the parent relation, so that they match
+ * up correctly against qual clauses.
+ */
+ if (varno != 1)
+ ChangeVarNodes((Node *) exprs, 1, varno, 0);
+ }
+ }
+
+ /* add one StatisticExtInfo for each kind built */
+ if (statext_is_kind_built(dtup, STATS_EXT_NDISTINCT))
+ {
+ StatisticExtInfo *info = makeNode(StatisticExtInfo);
+
+ info->statOid = statOid;
+ info->rel = rel;
+ info->kind = STATS_EXT_NDISTINCT;
+ info->keys = bms_copy(keys);
+ info->exprs = exprs;
+
+ stainfos = lappend(stainfos, info);
+ }
+
+ if (statext_is_kind_built(dtup, STATS_EXT_DEPENDENCIES))
+ {
+ StatisticExtInfo *info = makeNode(StatisticExtInfo);
+
+ info->statOid = statOid;
+ info->rel = rel;
+ info->kind = STATS_EXT_DEPENDENCIES;
+ info->keys = bms_copy(keys);
+ info->exprs = exprs;
+
+ stainfos = lappend(stainfos, info);
+ }
+
+ if (statext_is_kind_built(dtup, STATS_EXT_MCV))
+ {
+ StatisticExtInfo *info = makeNode(StatisticExtInfo);
+
+ info->statOid = statOid;
+ info->rel = rel;
+ info->kind = STATS_EXT_MCV;
+ info->keys = bms_copy(keys);
+ info->exprs = exprs;
+
+ stainfos = lappend(stainfos, info);
+ }
+
+ if (statext_is_kind_built(dtup, STATS_EXT_EXPRESSIONS))
+ {
+ StatisticExtInfo *info = makeNode(StatisticExtInfo);
+
+ info->statOid = statOid;
+ info->rel = rel;
+ info->kind = STATS_EXT_EXPRESSIONS;
+ info->keys = bms_copy(keys);
+ info->exprs = exprs;
+
+ stainfos = lappend(stainfos, info);
+ }
+
+ ReleaseSysCache(htup);
+ ReleaseSysCache(dtup);
+ bms_free(keys);
+ }
+
+ list_free(statoidlist);
+
+ return stainfos;
+}
+
+/*
+ * relation_excluded_by_constraints
+ *
+ * Detect whether the relation need not be scanned because it has either
+ * self-inconsistent restrictions, or restrictions inconsistent with the
+ * relation's applicable constraints.
+ *
+ * Note: this examines only rel->relid, rel->reloptkind, and
+ * rel->baserestrictinfo; therefore it can be called before filling in
+ * other fields of the RelOptInfo.
+ */
+bool
+relation_excluded_by_constraints(PlannerInfo *root,
+ RelOptInfo *rel, RangeTblEntry *rte)
+{
+ bool include_noinherit;
+ bool include_notnull;
+ bool include_partition = false;
+ List *safe_restrictions;
+ List *constraint_pred;
+ List *safe_constraints;
+ ListCell *lc;
+
+ /* As of now, constraint exclusion works only with simple relations. */
+ Assert(IS_SIMPLE_REL(rel));
+
+ /*
+ * If there are no base restriction clauses, we have no hope of proving
+ * anything below, so fall out quickly.
+ */
+ if (rel->baserestrictinfo == NIL)
+ return false;
+
+ /*
+ * Regardless of the setting of constraint_exclusion, detect
+ * constant-FALSE-or-NULL restriction clauses. Because const-folding will
+ * reduce "anything AND FALSE" to just "FALSE", any such case should
+ * result in exactly one baserestrictinfo entry. This doesn't fire very
+ * often, but it seems cheap enough to be worth doing anyway. (Without
+ * this, we'd miss some optimizations that 9.5 and earlier found via much
+ * more roundabout methods.)
+ */
+ if (list_length(rel->baserestrictinfo) == 1)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) linitial(rel->baserestrictinfo);
+ Expr *clause = rinfo->clause;
+
+ if (clause && IsA(clause, Const) &&
+ (((Const *) clause)->constisnull ||
+ !DatumGetBool(((Const *) clause)->constvalue)))
+ return true;
+ }
+
+ /*
+ * Skip further tests, depending on constraint_exclusion.
+ */
+ switch (constraint_exclusion)
+ {
+ case CONSTRAINT_EXCLUSION_OFF:
+ /* In 'off' mode, never make any further tests */
+ return false;
+
+ case CONSTRAINT_EXCLUSION_PARTITION:
+
+ /*
+ * When constraint_exclusion is set to 'partition' we only handle
+ * appendrel members. Partition pruning has already been applied,
+ * so there is no need to consider the rel's partition constraints
+ * here.
+ */
+ if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
+ break; /* appendrel member, so process it */
+ return false;
+
+ case CONSTRAINT_EXCLUSION_ON:
+
+ /*
+ * In 'on' mode, always apply constraint exclusion. If we are
+ * considering a baserel that is a partition (i.e., it was
+ * directly named rather than expanded from a parent table), then
+ * its partition constraints haven't been considered yet, so
+ * include them in the processing here.
+ */
+ if (rel->reloptkind == RELOPT_BASEREL)
+ include_partition = true;
+ break; /* always try to exclude */
+ }
+
+ /*
+ * Check for self-contradictory restriction clauses. We dare not make
+ * deductions with non-immutable functions, but any immutable clauses that
+ * are self-contradictory allow us to conclude the scan is unnecessary.
+ *
+ * Note: strip off RestrictInfo because predicate_refuted_by() isn't
+ * expecting to see any in its predicate argument.
+ */
+ safe_restrictions = NIL;
+ foreach(lc, rel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (!contain_mutable_functions((Node *) rinfo->clause))
+ safe_restrictions = lappend(safe_restrictions, rinfo->clause);
+ }
+
+ /*
+ * We can use weak refutation here, since we're comparing restriction
+ * clauses with restriction clauses.
+ */
+ if (predicate_refuted_by(safe_restrictions, safe_restrictions, true))
+ return true;
+
+ /*
+ * Only plain relations have constraints, so stop here for other rtekinds.
+ */
+ if (rte->rtekind != RTE_RELATION)
+ return false;
+
+ /*
+ * If we are scanning just this table, we can use NO INHERIT constraints,
+ * but not if we're scanning its children too. (Note that partitioned
+ * tables should never have NO INHERIT constraints; but it's not necessary
+ * for us to assume that here.)
+ */
+ include_noinherit = !rte->inh;
+
+ /*
+ * Currently, attnotnull constraints must be treated as NO INHERIT unless
+ * this is a partitioned table. In future we might track their
+ * inheritance status more accurately, allowing this to be refined.
+ */
+ include_notnull = (!rte->inh || rte->relkind == RELKIND_PARTITIONED_TABLE);
+
+ /*
+ * Fetch the appropriate set of constraint expressions.
+ */
+ constraint_pred = get_relation_constraints(root, rte->relid, rel,
+ include_noinherit,
+ include_notnull,
+ include_partition);
+
+ /*
+ * We do not currently enforce that CHECK constraints contain only
+ * immutable functions, so it's necessary to check here. We daren't draw
+ * conclusions from plan-time evaluation of non-immutable functions. Since
+ * they're ANDed, we can just ignore any mutable constraints in the list,
+ * and reason about the rest.
+ */
+ safe_constraints = NIL;
+ foreach(lc, constraint_pred)
+ {
+ Node *pred = (Node *) lfirst(lc);
+
+ if (!contain_mutable_functions(pred))
+ safe_constraints = lappend(safe_constraints, pred);
+ }
+
+ /*
+ * The constraints are effectively ANDed together, so we can just try to
+ * refute the entire collection at once. This may allow us to make proofs
+ * that would fail if we took them individually.
+ *
+ * Note: we use rel->baserestrictinfo, not safe_restrictions as might seem
+ * an obvious optimization. Some of the clauses might be OR clauses that
+ * have volatile and nonvolatile subclauses, and it's OK to make
+ * deductions with the nonvolatile parts.
+ *
+ * We need strong refutation because we have to prove that the constraints
+ * would yield false, not just NULL.
+ */
+ if (predicate_refuted_by(safe_constraints, rel->baserestrictinfo, false))
+ return true;
+
+ return false;
+}
+
+
+/*
+ * build_physical_tlist
+ *
+ * Build a targetlist consisting of exactly the relation's user attributes,
+ * in order. The executor can special-case such tlists to avoid a projection
+ * step at runtime, so we use such tlists preferentially for scan nodes.
+ *
+ * Exception: if there are any dropped or missing columns, we punt and return
+ * NIL. Ideally we would like to handle these cases too. However this
+ * creates problems for ExecTypeFromTL, which may be asked to build a tupdesc
+ * for a tlist that includes vars of no-longer-existent types. In theory we
+ * could dig out the required info from the pg_attribute entries of the
+ * relation, but that data is not readily available to ExecTypeFromTL.
+ * For now, we don't apply the physical-tlist optimization when there are
+ * dropped cols.
+ *
+ * We also support building a "physical" tlist for subqueries, functions,
+ * values lists, table expressions, and CTEs, since the same optimization can
+ * occur in SubqueryScan, FunctionScan, ValuesScan, CteScan, TableFunc,
+ * NamedTuplestoreScan, and WorkTableScan nodes.
+ */
+List *
+build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
+{
+ List *tlist = NIL;
+ Index varno = rel->relid;
+ RangeTblEntry *rte = planner_rt_fetch(varno, root);
+ Relation relation;
+ Query *subquery;
+ Var *var;
+ ListCell *l;
+ int attrno,
+ numattrs;
+ List *colvars;
+
+ switch (rte->rtekind)
+ {
+ case RTE_RELATION:
+ /* Assume we already have adequate lock */
+ relation = table_open(rte->relid, NoLock);
+
+ numattrs = RelationGetNumberOfAttributes(relation);
+ for (attrno = 1; attrno <= numattrs; attrno++)
+ {
+ Form_pg_attribute att_tup = TupleDescAttr(relation->rd_att,
+ attrno - 1);
+
+ if (att_tup->attisdropped || att_tup->atthasmissing)
+ {
+ /* found a dropped or missing col, so punt */
+ tlist = NIL;
+ break;
+ }
+
+ var = makeVar(varno,
+ attrno,
+ att_tup->atttypid,
+ att_tup->atttypmod,
+ att_tup->attcollation,
+ 0);
+
+ tlist = lappend(tlist,
+ makeTargetEntry((Expr *) var,
+ attrno,
+ NULL,
+ false));
+ }
+
+ table_close(relation, NoLock);
+ break;
+
+ case RTE_SUBQUERY:
+ subquery = rte->subquery;
+ foreach(l, subquery->targetList)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ /*
+ * A resjunk column of the subquery can be reflected as
+ * resjunk in the physical tlist; we need not punt.
+ */
+ var = makeVarFromTargetEntry(varno, tle);
+
+ tlist = lappend(tlist,
+ makeTargetEntry((Expr *) var,
+ tle->resno,
+ NULL,
+ tle->resjunk));
+ }
+ break;
+
+ case RTE_FUNCTION:
+ case RTE_TABLEFUNC:
+ case RTE_VALUES:
+ case RTE_CTE:
+ case RTE_NAMEDTUPLESTORE:
+ case RTE_RESULT:
+ /* Not all of these can have dropped cols, but share code anyway */
+ expandRTE(rte, varno, 0, -1, true /* include dropped */ ,
+ NULL, &colvars);
+ foreach(l, colvars)
+ {
+ var = (Var *) lfirst(l);
+
+ /*
+ * A non-Var in expandRTE's output means a dropped column;
+ * must punt.
+ */
+ if (!IsA(var, Var))
+ {
+ tlist = NIL;
+ break;
+ }
+
+ tlist = lappend(tlist,
+ makeTargetEntry((Expr *) var,
+ var->varattno,
+ NULL,
+ false));
+ }
+ break;
+
+ default:
+ /* caller error */
+ elog(ERROR, "unsupported RTE kind %d in build_physical_tlist",
+ (int) rte->rtekind);
+ break;
+ }
+
+ return tlist;
+}
+
+/*
+ * build_index_tlist
+ *
+ * Build a targetlist representing the columns of the specified index.
+ * Each column is represented by a Var for the corresponding base-relation
+ * column, or an expression in base-relation Vars, as appropriate.
+ *
+ * There are never any dropped columns in indexes, so unlike
+ * build_physical_tlist, we need no failure case.
+ */
+static List *
+build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
+ Relation heapRelation)
+{
+ List *tlist = NIL;
+ Index varno = index->rel->relid;
+ ListCell *indexpr_item;
+ int i;
+
+ indexpr_item = list_head(index->indexprs);
+ for (i = 0; i < index->ncolumns; i++)
+ {
+ int indexkey = index->indexkeys[i];
+ Expr *indexvar;
+
+ if (indexkey != 0)
+ {
+ /* simple column */
+ const FormData_pg_attribute *att_tup;
+
+ if (indexkey < 0)
+ att_tup = SystemAttributeDefinition(indexkey);
+ else
+ att_tup = TupleDescAttr(heapRelation->rd_att, indexkey - 1);
+
+ indexvar = (Expr *) makeVar(varno,
+ indexkey,
+ att_tup->atttypid,
+ att_tup->atttypmod,
+ att_tup->attcollation,
+ 0);
+ }
+ else
+ {
+ /* expression column */
+ if (indexpr_item == NULL)
+ elog(ERROR, "wrong number of index expressions");
+ indexvar = (Expr *) lfirst(indexpr_item);
+ indexpr_item = lnext(index->indexprs, indexpr_item);
+ }
+
+ tlist = lappend(tlist,
+ makeTargetEntry(indexvar,
+ i + 1,
+ NULL,
+ false));
+ }
+ if (indexpr_item != NULL)
+ elog(ERROR, "wrong number of index expressions");
+
+ return tlist;
+}
+
+/*
+ * restriction_selectivity
+ *
+ * Returns the selectivity of a specified restriction operator clause.
+ * This code executes registered procedures stored in the
+ * operator relation, by calling the function manager.
+ *
+ * See clause_selectivity() for the meaning of the additional parameters.
+ */
+Selectivity
+restriction_selectivity(PlannerInfo *root,
+ Oid operatorid,
+ List *args,
+ Oid inputcollid,
+ int varRelid)
+{
+ RegProcedure oprrest = get_oprrest(operatorid);
+ float8 result;
+
+ /*
+ * if the oprrest procedure is missing for whatever reason, use a
+ * selectivity of 0.5
+ */
+ if (!oprrest)
+ return (Selectivity) 0.5;
+
+ result = DatumGetFloat8(OidFunctionCall4Coll(oprrest,
+ inputcollid,
+ PointerGetDatum(root),
+ ObjectIdGetDatum(operatorid),
+ PointerGetDatum(args),
+ Int32GetDatum(varRelid)));
+
+ if (result < 0.0 || result > 1.0)
+ elog(ERROR, "invalid restriction selectivity: %f", result);
+
+ return (Selectivity) result;
+}
+
+/*
+ * join_selectivity
+ *
+ * Returns the selectivity of a specified join operator clause.
+ * This code executes registered procedures stored in the
+ * operator relation, by calling the function manager.
+ *
+ * See clause_selectivity() for the meaning of the additional parameters.
+ */
+Selectivity
+join_selectivity(PlannerInfo *root,
+ Oid operatorid,
+ List *args,
+ Oid inputcollid,
+ JoinType jointype,
+ SpecialJoinInfo *sjinfo)
+{
+ RegProcedure oprjoin = get_oprjoin(operatorid);
+ float8 result;
+
+ /*
+ * if the oprjoin procedure is missing for whatever reason, use a
+ * selectivity of 0.5
+ */
+ if (!oprjoin)
+ return (Selectivity) 0.5;
+
+ result = DatumGetFloat8(OidFunctionCall5Coll(oprjoin,
+ inputcollid,
+ PointerGetDatum(root),
+ ObjectIdGetDatum(operatorid),
+ PointerGetDatum(args),
+ Int16GetDatum(jointype),
+ PointerGetDatum(sjinfo)));
+
+ if (result < 0.0 || result > 1.0)
+ elog(ERROR, "invalid join selectivity: %f", result);
+
+ return (Selectivity) result;
+}
+
+/*
+ * function_selectivity
+ *
+ * Returns the selectivity of a specified boolean function clause.
+ * This code executes registered procedures stored in the
+ * pg_proc relation, by calling the function manager.
+ *
+ * See clause_selectivity() for the meaning of the additional parameters.
+ */
+Selectivity
+function_selectivity(PlannerInfo *root,
+ Oid funcid,
+ List *args,
+ Oid inputcollid,
+ bool is_join,
+ int varRelid,
+ JoinType jointype,
+ SpecialJoinInfo *sjinfo)
+{
+ RegProcedure prosupport = get_func_support(funcid);
+ SupportRequestSelectivity req;
+ SupportRequestSelectivity *sresult;
+
+ /*
+ * If no support function is provided, use our historical default
+ * estimate, 0.3333333. This seems a pretty unprincipled choice, but
+ * Postgres has been using that estimate for function calls since 1992.
+ * The hoariness of this behavior suggests that we should not be in too
+ * much hurry to use another value.
+ */
+ if (!prosupport)
+ return (Selectivity) 0.3333333;
+
+ req.type = T_SupportRequestSelectivity;
+ req.root = root;
+ req.funcid = funcid;
+ req.args = args;
+ req.inputcollid = inputcollid;
+ req.is_join = is_join;
+ req.varRelid = varRelid;
+ req.jointype = jointype;
+ req.sjinfo = sjinfo;
+ req.selectivity = -1; /* to catch failure to set the value */
+
+ sresult = (SupportRequestSelectivity *)
+ DatumGetPointer(OidFunctionCall1(prosupport,
+ PointerGetDatum(&req)));
+
+ /* If support function fails, use default */
+ if (sresult != &req)
+ return (Selectivity) 0.3333333;
+
+ if (req.selectivity < 0.0 || req.selectivity > 1.0)
+ elog(ERROR, "invalid function selectivity: %f", req.selectivity);
+
+ return (Selectivity) req.selectivity;
+}
+
+/*
+ * add_function_cost
+ *
+ * Get an estimate of the execution cost of a function, and *add* it to
+ * the contents of *cost. The estimate may include both one-time and
+ * per-tuple components, since QualCost does.
+ *
+ * The funcid must always be supplied. If it is being called as the
+ * implementation of a specific parsetree node (FuncExpr, OpExpr,
+ * WindowFunc, etc), pass that as "node", else pass NULL.
+ *
+ * In some usages root might be NULL, too.
+ */
+void
+add_function_cost(PlannerInfo *root, Oid funcid, Node *node,
+ QualCost *cost)
+{
+ HeapTuple proctup;
+ Form_pg_proc procform;
+
+ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(proctup))
+ elog(ERROR, "cache lookup failed for function %u", funcid);
+ procform = (Form_pg_proc) GETSTRUCT(proctup);
+
+ if (OidIsValid(procform->prosupport))
+ {
+ SupportRequestCost req;
+ SupportRequestCost *sresult;
+
+ req.type = T_SupportRequestCost;
+ req.root = root;
+ req.funcid = funcid;
+ req.node = node;
+
+ /* Initialize cost fields so that support function doesn't have to */
+ req.startup = 0;
+ req.per_tuple = 0;
+
+ sresult = (SupportRequestCost *)
+ DatumGetPointer(OidFunctionCall1(procform->prosupport,
+ PointerGetDatum(&req)));
+
+ if (sresult == &req)
+ {
+ /* Success, so accumulate support function's estimate into *cost */
+ cost->startup += req.startup;
+ cost->per_tuple += req.per_tuple;
+ ReleaseSysCache(proctup);
+ return;
+ }
+ }
+
+ /* No support function, or it failed, so rely on procost */
+ cost->per_tuple += procform->procost * cpu_operator_cost;
+
+ ReleaseSysCache(proctup);
+}
+
+/*
+ * get_function_rows
+ *
+ * Get an estimate of the number of rows returned by a set-returning function.
+ *
+ * The funcid must always be supplied. In current usage, the calling node
+ * will always be supplied, and will be either a FuncExpr or OpExpr.
+ * But it's a good idea to not fail if it's NULL.
+ *
+ * In some usages root might be NULL, too.
+ *
+ * Note: this returns the unfiltered result of the support function, if any.
+ * It's usually a good idea to apply clamp_row_est() to the result, but we
+ * leave it to the caller to do so.
+ */
+double
+get_function_rows(PlannerInfo *root, Oid funcid, Node *node)
+{
+ HeapTuple proctup;
+ Form_pg_proc procform;
+ double result;
+
+ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(proctup))
+ elog(ERROR, "cache lookup failed for function %u", funcid);
+ procform = (Form_pg_proc) GETSTRUCT(proctup);
+
+ Assert(procform->proretset); /* else caller error */
+
+ if (OidIsValid(procform->prosupport))
+ {
+ SupportRequestRows req;
+ SupportRequestRows *sresult;
+
+ req.type = T_SupportRequestRows;
+ req.root = root;
+ req.funcid = funcid;
+ req.node = node;
+
+ req.rows = 0; /* just for sanity */
+
+ sresult = (SupportRequestRows *)
+ DatumGetPointer(OidFunctionCall1(procform->prosupport,
+ PointerGetDatum(&req)));
+
+ if (sresult == &req)
+ {
+ /* Success */
+ ReleaseSysCache(proctup);
+ return req.rows;
+ }
+ }
+
+ /* No support function, or it failed, so rely on prorows */
+ result = procform->prorows;
+
+ ReleaseSysCache(proctup);
+
+ return result;
+}
+
+/*
+ * has_unique_index
+ *
+ * Detect whether there is a unique index on the specified attribute
+ * of the specified relation, thus allowing us to conclude that all
+ * the (non-null) values of the attribute are distinct.
+ *
+ * This function does not check the index's indimmediate property, which
+ * means that uniqueness may transiently fail to hold intra-transaction.
+ * That's appropriate when we are making statistical estimates, but beware
+ * of using this for any correctness proofs.
+ */
+bool
+has_unique_index(RelOptInfo *rel, AttrNumber attno)
+{
+ ListCell *ilist;
+
+ foreach(ilist, rel->indexlist)
+ {
+ IndexOptInfo *index = (IndexOptInfo *) lfirst(ilist);
+
+ /*
+ * Note: ignore partial indexes, since they don't allow us to conclude
+ * that all attr values are distinct, *unless* they are marked predOK
+ * which means we know the index's predicate is satisfied by the
+ * query. We don't take any interest in expressional indexes either.
+ * Also, a multicolumn unique index doesn't allow us to conclude that
+ * just the specified attr is unique.
+ */
+ if (index->unique &&
+ index->nkeycolumns == 1 &&
+ index->indexkeys[0] == attno &&
+ (index->indpred == NIL || index->predOK))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * has_row_triggers
+ *
+ * Detect whether the specified relation has any row-level triggers for event.
+ */
+bool
+has_row_triggers(PlannerInfo *root, Index rti, CmdType event)
+{
+ RangeTblEntry *rte = planner_rt_fetch(rti, root);
+ Relation relation;
+ TriggerDesc *trigDesc;
+ bool result = false;
+
+ /* Assume we already have adequate lock */
+ relation = table_open(rte->relid, NoLock);
+
+ trigDesc = relation->trigdesc;
+ switch (event)
+ {
+ case CMD_INSERT:
+ if (trigDesc &&
+ (trigDesc->trig_insert_after_row ||
+ trigDesc->trig_insert_before_row))
+ result = true;
+ break;
+ case CMD_UPDATE:
+ if (trigDesc &&
+ (trigDesc->trig_update_after_row ||
+ trigDesc->trig_update_before_row))
+ result = true;
+ break;
+ case CMD_DELETE:
+ if (trigDesc &&
+ (trigDesc->trig_delete_after_row ||
+ trigDesc->trig_delete_before_row))
+ result = true;
+ break;
+ default:
+ elog(ERROR, "unrecognized CmdType: %d", (int) event);
+ break;
+ }
+
+ table_close(relation, NoLock);
+ return result;
+}
+
+bool
+has_stored_generated_columns(PlannerInfo *root, Index rti)
+{
+ RangeTblEntry *rte = planner_rt_fetch(rti, root);
+ Relation relation;
+ TupleDesc tupdesc;
+ bool result = false;
+
+ /* Assume we already have adequate lock */
+ relation = table_open(rte->relid, NoLock);
+
+ tupdesc = RelationGetDescr(relation);
+ result = tupdesc->constr && tupdesc->constr->has_generated_stored;
+
+ table_close(relation, NoLock);
+
+ return result;
+}
+
+/*
+ * set_relation_partition_info
+ *
+ * Set partitioning scheme and related information for a partitioned table.
+ */
+static void
+set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel,
+ Relation relation)
+{
+ PartitionDesc partdesc;
+
+ /*
+ * Create the PartitionDirectory infrastructure if we didn't already.
+ */
+ if (root->glob->partition_directory == NULL)
+ {
+ root->glob->partition_directory =
+ CreatePartitionDirectory(CurrentMemoryContext, true);
+ }
+
+ partdesc = PartitionDirectoryLookup(root->glob->partition_directory,
+ relation);
+ rel->part_scheme = find_partition_scheme(root, relation);
+ Assert(partdesc != NULL && rel->part_scheme != NULL);
+ rel->boundinfo = partdesc->boundinfo;
+ rel->nparts = partdesc->nparts;
+ set_baserel_partition_key_exprs(relation, rel);
+ set_baserel_partition_constraint(relation, rel);
+}
+
+/*
+ * find_partition_scheme
+ *
+ * Find or create a PartitionScheme for this Relation.
+ */
+static PartitionScheme
+find_partition_scheme(PlannerInfo *root, Relation relation)
+{
+ PartitionKey partkey = RelationGetPartitionKey(relation);
+ ListCell *lc;
+ int partnatts,
+ i;
+ PartitionScheme part_scheme;
+
+ /* A partitioned table should have a partition key. */
+ Assert(partkey != NULL);
+
+ partnatts = partkey->partnatts;
+
+ /* Search for a matching partition scheme and return if found one. */
+ foreach(lc, root->part_schemes)
+ {
+ part_scheme = lfirst(lc);
+
+ /* Match partitioning strategy and number of keys. */
+ if (partkey->strategy != part_scheme->strategy ||
+ partnatts != part_scheme->partnatts)
+ continue;
+
+ /* Match partition key type properties. */
+ if (memcmp(partkey->partopfamily, part_scheme->partopfamily,
+ sizeof(Oid) * partnatts) != 0 ||
+ memcmp(partkey->partopcintype, part_scheme->partopcintype,
+ sizeof(Oid) * partnatts) != 0 ||
+ memcmp(partkey->partcollation, part_scheme->partcollation,
+ sizeof(Oid) * partnatts) != 0)
+ continue;
+
+ /*
+ * Length and byval information should match when partopcintype
+ * matches.
+ */
+ Assert(memcmp(partkey->parttyplen, part_scheme->parttyplen,
+ sizeof(int16) * partnatts) == 0);
+ Assert(memcmp(partkey->parttypbyval, part_scheme->parttypbyval,
+ sizeof(bool) * partnatts) == 0);
+
+ /*
+ * If partopfamily and partopcintype matched, must have the same
+ * partition comparison functions. Note that we cannot reliably
+ * Assert the equality of function structs themselves for they might
+ * be different across PartitionKey's, so just Assert for the function
+ * OIDs.
+ */
+#ifdef USE_ASSERT_CHECKING
+ for (i = 0; i < partkey->partnatts; i++)
+ Assert(partkey->partsupfunc[i].fn_oid ==
+ part_scheme->partsupfunc[i].fn_oid);
+#endif
+
+ /* Found matching partition scheme. */
+ return part_scheme;
+ }
+
+ /*
+ * Did not find matching partition scheme. Create one copying relevant
+ * information from the relcache. We need to copy the contents of the
+ * array since the relcache entry may not survive after we have closed the
+ * relation.
+ */
+ part_scheme = (PartitionScheme) palloc0(sizeof(PartitionSchemeData));
+ part_scheme->strategy = partkey->strategy;
+ part_scheme->partnatts = partkey->partnatts;
+
+ part_scheme->partopfamily = (Oid *) palloc(sizeof(Oid) * partnatts);
+ memcpy(part_scheme->partopfamily, partkey->partopfamily,
+ sizeof(Oid) * partnatts);
+
+ part_scheme->partopcintype = (Oid *) palloc(sizeof(Oid) * partnatts);
+ memcpy(part_scheme->partopcintype, partkey->partopcintype,
+ sizeof(Oid) * partnatts);
+
+ part_scheme->partcollation = (Oid *) palloc(sizeof(Oid) * partnatts);
+ memcpy(part_scheme->partcollation, partkey->partcollation,
+ sizeof(Oid) * partnatts);
+
+ part_scheme->parttyplen = (int16 *) palloc(sizeof(int16) * partnatts);
+ memcpy(part_scheme->parttyplen, partkey->parttyplen,
+ sizeof(int16) * partnatts);
+
+ part_scheme->parttypbyval = (bool *) palloc(sizeof(bool) * partnatts);
+ memcpy(part_scheme->parttypbyval, partkey->parttypbyval,
+ sizeof(bool) * partnatts);
+
+ part_scheme->partsupfunc = (FmgrInfo *)
+ palloc(sizeof(FmgrInfo) * partnatts);
+ for (i = 0; i < partnatts; i++)
+ fmgr_info_copy(&part_scheme->partsupfunc[i], &partkey->partsupfunc[i],
+ CurrentMemoryContext);
+
+ /* Add the partitioning scheme to PlannerInfo. */
+ root->part_schemes = lappend(root->part_schemes, part_scheme);
+
+ return part_scheme;
+}
+
+/*
+ * set_baserel_partition_key_exprs
+ *
+ * Builds partition key expressions for the given base relation and fills
+ * rel->partexprs.
+ */
+static void
+set_baserel_partition_key_exprs(Relation relation,
+ RelOptInfo *rel)
+{
+ PartitionKey partkey = RelationGetPartitionKey(relation);
+ int partnatts;
+ int cnt;
+ List **partexprs;
+ ListCell *lc;
+ Index varno = rel->relid;
+
+ Assert(IS_SIMPLE_REL(rel) && rel->relid > 0);
+
+ /* A partitioned table should have a partition key. */
+ Assert(partkey != NULL);
+
+ partnatts = partkey->partnatts;
+ partexprs = (List **) palloc(sizeof(List *) * partnatts);
+ lc = list_head(partkey->partexprs);
+
+ for (cnt = 0; cnt < partnatts; cnt++)
+ {
+ Expr *partexpr;
+ AttrNumber attno = partkey->partattrs[cnt];
+
+ if (attno != InvalidAttrNumber)
+ {
+ /* Single column partition key is stored as a Var node. */
+ Assert(attno > 0);
+
+ partexpr = (Expr *) makeVar(varno, attno,
+ partkey->parttypid[cnt],
+ partkey->parttypmod[cnt],
+ partkey->parttypcoll[cnt], 0);
+ }
+ else
+ {
+ if (lc == NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+
+ /* Re-stamp the expression with given varno. */
+ partexpr = (Expr *) copyObject(lfirst(lc));
+ ChangeVarNodes((Node *) partexpr, 1, varno, 0);
+ lc = lnext(partkey->partexprs, lc);
+ }
+
+ /* Base relations have a single expression per key. */
+ partexprs[cnt] = list_make1(partexpr);
+ }
+
+ rel->partexprs = partexprs;
+
+ /*
+ * A base relation does not have nullable partition key expressions, since
+ * no outer join is involved. We still allocate an array of empty
+ * expression lists to keep partition key expression handling code simple.
+ * See build_joinrel_partition_info() and match_expr_to_partition_keys().
+ */
+ rel->nullable_partexprs = (List **) palloc0(sizeof(List *) * partnatts);
+}
+
+/*
+ * set_baserel_partition_constraint
+ *
+ * Builds the partition constraint for the given base relation and sets it
+ * in the given RelOptInfo. All Var nodes are restamped with the relid of the
+ * given relation.
+ */
+static void
+set_baserel_partition_constraint(Relation relation, RelOptInfo *rel)
+{
+ List *partconstr;
+
+ if (rel->partition_qual) /* already done */
+ return;
+
+ /*
+ * Run the partition quals through const-simplification similar to check
+ * constraints. We skip canonicalize_qual, though, because partition
+ * quals should be in canonical form already; also, since the qual is in
+ * implicit-AND format, we'd have to explicitly convert it to explicit-AND
+ * format and back again.
+ */
+ partconstr = RelationGetPartitionQual(relation);
+ if (partconstr)
+ {
+ partconstr = (List *) expression_planner((Expr *) partconstr);
+ if (rel->relid != 1)
+ ChangeVarNodes((Node *) partconstr, 1, rel->relid, 0);
+ rel->partition_qual = partconstr;
+ }
+}