/* * 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 * pages number of pages * tuples number of tuples * * 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 = heap_open(relationObjectId, NoLock); /* Temporary and unlogged relations are inaccessible during recovery. */ if (!RelationNeedsWAL(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 will be computed later in set_append_rel_pathlist, and we * must leave it zero for now to avoid bollixing the total_table_pages * calculation. */ if (!inhparent) estimate_rel_size(relation, rel->attr_widths - rel->min_attr, &rel->pages, &rel->tuples, &rel->allvisfrac); /* * 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 && IsSystemClass(relation->rd_rel))) hasindex = false; else hasindex = relation->rd_rel->relhasindex; if (hasindex) { List *indexoidlist; ListCell *l; LOCKMODE lmode; 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 neither lock type * blocks any other kind of index operation. */ if (rel->relid == root->parse->resultRelation) lmode = RowExclusiveLock; else lmode = AccessShareLock; foreach(l, indexoidlist) { Oid indexoid = lfirst_oid(l); Relation indexRelation; Form_pg_index index; IndexOptInfo *info; int ncolumns; 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 (!index->indisvalid) { 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->indexkeys = (int *) palloc(sizeof(int) * ncolumns); info->indexcollations = (Oid *) palloc(sizeof(Oid) * ncolumns); info->opfamily = (Oid *) palloc(sizeof(Oid) * ncolumns); info->opcintype = (Oid *) palloc(sizeof(Oid) * ncolumns); for (i = 0; i < ncolumns; i++) { info->indexkeys[i] = index->indkey.values[i]; info->indexcollations[i] = indexRelation->rd_indcollation[i]; info->opfamily[i] = indexRelation->rd_opfamily[i]; info->opcintype[i] = indexRelation->rd_opcintype[i]; } info->relam = indexRelation->rd_rel->relam; info->amcostestimate = indexRelation->rd_am->amcostestimate; info->canreturn = index_can_return(indexRelation); info->amcanorderbyop = indexRelation->rd_am->amcanorderbyop; info->amoptionalkey = indexRelation->rd_am->amoptionalkey; info->amsearcharray = indexRelation->rd_am->amsearcharray; info->amsearchnulls = indexRelation->rd_am->amsearchnulls; info->amhasgettuple = OidIsValid(indexRelation->rd_am->amgettuple); info->amhasgetbitmap = OidIsValid(indexRelation->rd_am->amgetbitmap); /* * 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(indexRelation->rd_am->amcanorder); info->sortopfamily = info->opfamily; info->reverse_sort = (bool *) palloc(sizeof(bool) * ncolumns); info->nulls_first = (bool *) palloc(sizeof(bool) * ncolumns); for (i = 0; i < ncolumns; 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 (indexRelation->rd_am->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) * ncolumns); info->reverse_sort = (bool *) palloc(sizeof(bool) * ncolumns); info->nulls_first = (bool *) palloc(sizeof(bool) * ncolumns); for (i = 0; i < ncolumns; 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->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; } index_close(indexRelation, NoLock); indexinfos = lcons(info, indexinfos); } list_free(indexoidlist); }
/* * Test property of an index AM, index, or index column. * * This is common code for different SQL-level funcs, so the amoid and * index_oid parameters are mutually exclusive; we look up the amoid from the * index_oid if needed, or if no index oid is given, we're looking at AM-wide * properties. */ static Datum indexam_property(FunctionCallInfo fcinfo, const char *propname, Oid amoid, Oid index_oid, int attno) { bool res = false; bool isnull = false; int natts = 0; IndexAMProperty prop; IndexAmRoutine *routine; /* Try to convert property name to enum (no error if not known) */ prop = lookup_prop_name(propname); /* If we have an index OID, look up the AM, and get # of columns too */ if (OidIsValid(index_oid)) { HeapTuple tuple; Form_pg_class rd_rel; Assert(!OidIsValid(amoid)); tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(index_oid)); if (!HeapTupleIsValid(tuple)) PG_RETURN_NULL(); rd_rel = (Form_pg_class) GETSTRUCT(tuple); if (rd_rel->relkind != RELKIND_INDEX && rd_rel->relkind != RELKIND_PARTITIONED_INDEX) { ReleaseSysCache(tuple); PG_RETURN_NULL(); } amoid = rd_rel->relam; natts = rd_rel->relnatts; ReleaseSysCache(tuple); } /* * At this point, either index_oid == InvalidOid or it's a valid index * OID. Also, after this test and the one below, either attno == 0 for * index-wide or AM-wide tests, or it's a valid column number in a valid * index. */ if (attno < 0 || attno > natts) PG_RETURN_NULL(); /* * Get AM information. If we don't have a valid AM OID, return NULL. */ routine = GetIndexAmRoutineByAmId(amoid, true); if (routine == NULL) PG_RETURN_NULL(); /* * If there's an AM property routine, give it a chance to override the * generic logic. Proceed if it returns false. */ if (routine->amproperty && routine->amproperty(index_oid, attno, prop, propname, &res, &isnull)) { if (isnull) PG_RETURN_NULL(); PG_RETURN_BOOL(res); } if (attno > 0) { HeapTuple tuple; Form_pg_index rd_index; bool iskey = true; /* * Handle column-level properties. Many of these need the pg_index row * (which we also need to use to check for nonkey atts) so we fetch * that first. */ tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid)); if (!HeapTupleIsValid(tuple)) PG_RETURN_NULL(); rd_index = (Form_pg_index) GETSTRUCT(tuple); Assert(index_oid == rd_index->indexrelid); Assert(attno > 0 && attno <= rd_index->indnatts); isnull = true; /* * If amcaninclude, we might be looking at an attno for a nonkey * column, for which we (generically) assume that most properties are * null. */ if (routine->amcaninclude && attno > rd_index->indnkeyatts) iskey = false; switch (prop) { case AMPROP_ASC: if (iskey && test_indoption(tuple, attno, routine->amcanorder, INDOPTION_DESC, 0, &res)) isnull = false; break; case AMPROP_DESC: if (iskey && test_indoption(tuple, attno, routine->amcanorder, INDOPTION_DESC, INDOPTION_DESC, &res)) isnull = false; break; case AMPROP_NULLS_FIRST: if (iskey && test_indoption(tuple, attno, routine->amcanorder, INDOPTION_NULLS_FIRST, INDOPTION_NULLS_FIRST, &res)) isnull = false; break; case AMPROP_NULLS_LAST: if (iskey && test_indoption(tuple, attno, routine->amcanorder, INDOPTION_NULLS_FIRST, 0, &res)) isnull = false; break; case AMPROP_ORDERABLE: /* * generic assumption is that nonkey columns are not orderable */ res = iskey ? routine->amcanorder : false; isnull = false; break; case AMPROP_DISTANCE_ORDERABLE: /* * The conditions for whether a column is distance-orderable * are really up to the AM (at time of writing, only GiST * supports it at all). The planner has its own idea based on * whether it finds an operator with amoppurpose 'o', but * getting there from just the index column type seems like a * lot of work. So instead we expect the AM to handle this in * its amproperty routine. The generic result is to return * false if the AM says it never supports this, or if this is * a nonkey column, and null otherwise (meaning we don't * know). */ if (!iskey || !routine->amcanorderbyop) { res = false; isnull = false; } break; case AMPROP_RETURNABLE: /* note that we ignore iskey for this property */ isnull = false; res = false; if (routine->amcanreturn) { /* * If possible, the AM should handle this test in its * amproperty function without opening the rel. But this * is the generic fallback if it does not. */ Relation indexrel = index_open(index_oid, AccessShareLock); res = index_can_return(indexrel, attno); index_close(indexrel, AccessShareLock); } break; case AMPROP_SEARCH_ARRAY: if (iskey) { res = routine->amsearcharray; isnull = false; } break; case AMPROP_SEARCH_NULLS: if (iskey) { res = routine->amsearchnulls; isnull = false; } break; default: break; } ReleaseSysCache(tuple); if (!isnull) PG_RETURN_BOOL(res); PG_RETURN_NULL(); } if (OidIsValid(index_oid)) { /* * Handle index-level properties. Currently, these only depend on the * AM, but that might not be true forever, so we make users name an * index not just an AM. */ switch (prop) { case AMPROP_CLUSTERABLE: PG_RETURN_BOOL(routine->amclusterable); case AMPROP_INDEX_SCAN: PG_RETURN_BOOL(routine->amgettuple ? true : false); case AMPROP_BITMAP_SCAN: PG_RETURN_BOOL(routine->amgetbitmap ? true : false); case AMPROP_BACKWARD_SCAN: PG_RETURN_BOOL(routine->amcanbackward); default: PG_RETURN_NULL(); } } /* * Handle AM-level properties (those that control what you can say in * CREATE INDEX). */ switch (prop) { case AMPROP_CAN_ORDER: PG_RETURN_BOOL(routine->amcanorder); case AMPROP_CAN_UNIQUE: PG_RETURN_BOOL(routine->amcanunique); case AMPROP_CAN_MULTI_COL: PG_RETURN_BOOL(routine->amcanmulticol); case AMPROP_CAN_EXCLUDE: PG_RETURN_BOOL(routine->amgettuple ? true : false); case AMPROP_CAN_INCLUDE: PG_RETURN_BOOL(routine->amcaninclude); default: PG_RETURN_NULL(); } }