in src/backend/optimizer/util/plancat.c [74:517]
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));
/*
* CDB: Get partitioning key info for distributed relation.
*/
rel->cdbpolicy = RelationGetPartitioningKey(relation);
rel->amhandler = relation->rd_amhandler;
/*
* 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)
{
cdb_estimate_rel_size(
rel,
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 (IsIndexAccessMethod(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.
*/
double allvisfrac; /* dummy */
cdb_estimate_rel_size(rel,
indexRelation,
NULL,
&info->pages,
&info->tuples,
&allvisfrac);
if (!info->indpred ||
info->tuples > rel->tuples)
info->tuples = rel->tuples;
if (IsIndexAccessMethod(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->segSeverids = GetForeignServerSegsByRelId(RelationGetRelid(relation));
rel->fdwroutine = GetFdwRoutineForRelation(relation, true);
rel->exec_location = GetForeignTable(RelationGetRelid(relation))->exec_location;
rel->num_segments = GetForeignTable(RelationGetRelid(relation))->num_segments;
}
else
{
rel->serverid = InvalidOid;
rel->segSeverids = NIL;
rel->fdwroutine = NULL;
rel->exec_location = FTEXECLOCATION_NOT_DEFINED;
rel->num_segments = getgpsegmentCount();
}
/* 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 store information, if it support column-oriented scan */
if (relation->rd_tableam && relation->rd_tableam->scan_flags &&
(relation->rd_tableam->scan_flags(relation) & SCAN_SUPPORT_COLUMN_ORIENTED_SCAN)) {
rel->amflags |= AMFLAG_HAS_COLUMN_ORIENTED_SCAN;
}
/*
* 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);
}