* relcache.c
* POSTGRES relation descriptor cache code
*
- * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.228 2005/08/26 03:07:48 tgl Exp $
+ * src/backend/utils/cache/relcache.c
*
*-------------------------------------------------------------------------
*/
/*
* INTERFACE ROUTINES
- * RelationCacheInitialize - initialize relcache
- * RelationCacheInitializePhase2 - finish initializing relcache
+ * RelationCacheInitialize - initialize relcache (to empty)
+ * RelationCacheInitializePhase2 - initialize shared-catalog entries
+ * RelationCacheInitializePhase3 - finish initializing relcache
* RelationIdGetRelation - get a reldesc by relation id
- * RelationIdCacheGetRelation - get a cached reldesc by relid
* RelationClose - close an open relation
*
* NOTES
#include <unistd.h>
#include "access/genam.h"
-#include "access/heapam.h"
+#include "access/reloptions.h"
+#include "access/sysattr.h"
+#include "access/transam.h"
+#include "access/xact.h"
#include "catalog/catalog.h"
+#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
-#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
-#include "catalog/pg_attribute.h"
#include "catalog/pg_authid.h"
+#include "catalog/pg_auth_members.h"
#include "catalog/pg_constraint.h"
-#include "catalog/pg_index.h"
+#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
+#include "catalog/pg_tablespace.h"
+#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
+#include "catalog/schemapg.h"
+#include "catalog/storage.h"
#include "commands/trigger.h"
#include "miscadmin.h"
#include "optimizer/clauses.h"
#include "optimizer/planmain.h"
#include "optimizer/prep.h"
+#include "optimizer/var.h"
+#include "rewrite/rewriteDefine.h"
#include "storage/fd.h"
+#include "storage/lmgr.h"
#include "storage/smgr.h"
+#include "utils/array.h"
#include "utils/builtins.h"
-#include "utils/catcache.h"
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/relcache.h"
+#include "utils/relmapper.h"
#include "utils/resowner.h"
#include "utils/syscache.h"
-#include "utils/typcache.h"
+#include "utils/tqual.h"
/*
- * name of relcache init file, used to speed up backend startup
+ * name of relcache init file(s), used to speed up backend startup
*/
#define RELCACHE_INIT_FILENAME "pg_internal.init"
-#define RELCACHE_INIT_FILEMAGIC 0x573262 /* version ID value */
+#define RELCACHE_INIT_FILEMAGIC 0x573266 /* version ID value */
/*
- * hardcoded tuple descriptors. see include/catalog/pg_attribute.h
+ * hardcoded tuple descriptors, contents generated by genbki.pl
*/
-static FormData_pg_attribute Desc_pg_class[Natts_pg_class] = {Schema_pg_class};
-static FormData_pg_attribute Desc_pg_attribute[Natts_pg_attribute] = {Schema_pg_attribute};
-static FormData_pg_attribute Desc_pg_proc[Natts_pg_proc] = {Schema_pg_proc};
-static FormData_pg_attribute Desc_pg_type[Natts_pg_type] = {Schema_pg_type};
-static FormData_pg_attribute Desc_pg_index[Natts_pg_index] = {Schema_pg_index};
+static const FormData_pg_attribute Desc_pg_class[Natts_pg_class] = {Schema_pg_class};
+static const FormData_pg_attribute Desc_pg_attribute[Natts_pg_attribute] = {Schema_pg_attribute};
+static const FormData_pg_attribute Desc_pg_proc[Natts_pg_proc] = {Schema_pg_proc};
+static const FormData_pg_attribute Desc_pg_type[Natts_pg_type] = {Schema_pg_type};
+static const FormData_pg_attribute Desc_pg_database[Natts_pg_database] = {Schema_pg_database};
+static const FormData_pg_attribute Desc_pg_authid[Natts_pg_authid] = {Schema_pg_authid};
+static const FormData_pg_attribute Desc_pg_auth_members[Natts_pg_auth_members] = {Schema_pg_auth_members};
+static const FormData_pg_attribute Desc_pg_index[Natts_pg_index] = {Schema_pg_index};
/*
* Hash tables that index the relation cache
bool criticalRelcachesBuilt = false;
/*
- * This flag is set if we discover that we need to write a new relcache
- * cache file at the end of startup.
+ * This flag is false until we have prepared the critical relcache entries
+ * for shared catalogs (which are the tables needed for login).
*/
-static bool needNewCacheFile = false;
+bool criticalSharedRelcachesBuilt = false;
/*
* This counter counts relcache inval events received since backend startup
static long relcacheInvalsReceived = 0L;
/*
- * This list remembers the OIDs of the relations cached in the relcache
- * init file.
+ * This list remembers the OIDs of the non-shared relations cached in the
+ * database's local relcache init file. Note that there is no corresponding
+ * list for the shared relcache init file, for reasons explained in the
+ * comments for RelationCacheInitFileRemove.
*/
static List *initFileRelationIds = NIL;
RelIdCacheEnt *idhentry; bool found; \
idhentry = (RelIdCacheEnt*)hash_search(RelationIdCache, \
(void *) &(RELATION->rd_id), \
- HASH_ENTER, \
- &found); \
+ HASH_ENTER, &found); \
/* used to give notice if found -- now just keep quiet */ \
idhentry->reldesc = RELATION; \
} while(0)
do { \
RelIdCacheEnt *hentry; \
hentry = (RelIdCacheEnt*)hash_search(RelationIdCache, \
- (void *) &(ID), HASH_FIND,NULL); \
+ (void *) &(ID), \
+ HASH_FIND, NULL); \
if (hentry) \
RELATION = hentry->reldesc; \
else \
/*
* Special cache for opclass-related information
*
- * Note: only default-subtype operators and support procs get cached
+ * Note: only default support procs get cached, ie, those with
+ * lefttype = righttype = opcintype.
*/
typedef struct opclasscacheent
{
Oid opclassoid; /* lookup key: OID of opclass */
bool valid; /* set TRUE after successful fill-in */
- StrategyNumber numStrats; /* max # of strategies (from pg_am) */
StrategyNumber numSupport; /* max # of support procs (from pg_am) */
- Oid *operatorOids; /* strategy operators' OIDs */
- RegProcedure *supportProcs; /* support procs */
+ Oid opcfamily; /* OID of opclass's family */
+ Oid opcintype; /* OID of opclass's declared input type */
+ RegProcedure *supportProcs; /* OIDs of support procedures */
} OpClassCacheEnt;
static HTAB *OpClassCache = NULL;
/* non-export function prototypes */
+static void RelationDestroyRelation(Relation relation);
static void RelationClearRelation(Relation relation, bool rebuild);
-static void RelationReloadClassinfo(Relation relation);
+static void RelationReloadIndexInfo(Relation relation);
static void RelationFlushRelation(Relation relation);
-static bool load_relcache_init_file(void);
-static void write_relcache_init_file(void);
+static bool load_relcache_init_file(bool shared);
+static void write_relcache_init_file(bool shared);
+static void write_item(const void *data, Size len, FILE *fp);
static void formrdesc(const char *relationName, Oid relationReltype,
- bool hasoids, int natts, FormData_pg_attribute *att);
+ bool isshared, bool hasoids,
+ int natts, const FormData_pg_attribute *attrs);
static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK);
-static Relation AllocateRelationDesc(Relation relation, Form_pg_class relp);
+static Relation AllocateRelationDesc(Form_pg_class relp);
+static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
static void RelationBuildTupleDesc(Relation relation);
-static Relation RelationBuildDesc(Oid targetRelId, Relation oldrelation);
+static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
static void RelationInitPhysicalAddr(Relation relation);
+static void load_critical_index(Oid indexoid, Oid heapoid);
+static TupleDesc GetPgClassDescriptor(void);
static TupleDesc GetPgIndexDescriptor(void);
static void AttrDefaultFetch(Relation relation);
static void CheckConstraintFetch(Relation relation);
static List *insert_ordered_oid(List *list, Oid datum);
static void IndexSupportInitialize(oidvector *indclass,
- Oid *indexOperator,
RegProcedure *indexSupport,
- StrategyNumber maxStrategyNumber,
+ Oid *opFamily,
+ Oid *opcInType,
StrategyNumber maxSupportNumber,
AttrNumber maxAttributeNumber);
static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
- StrategyNumber numStrats,
StrategyNumber numSupport);
+static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
+static void unlink_initfile(const char *initfilename);
/*
* ScanPgRelation
*
- * this is used by RelationBuildDesc to find a pg_class
- * tuple matching targetRelId.
+ * This is used by RelationBuildDesc to find a pg_class
+ * tuple matching targetRelId. The caller must hold at least
+ * AccessShareLock on the target relid to prevent concurrent-update
+ * scenarios --- else our SnapshotNow scan might fail to find any
+ * version that it thinks is live.
*
* NB: the returned tuple has been copied into palloc'd storage
* and must eventually be freed with heap_freetuple.
ScanKeyData key[1];
/*
+ * If something goes wrong during backend startup, we might find ourselves
+ * trying to read pg_class before we've selected a database. That ain't
+ * gonna work, so bail out with a useful error message. If this happens,
+ * it probably means a relcache entry that needs to be nailed isn't.
+ */
+ if (!OidIsValid(MyDatabaseId))
+ elog(FATAL, "cannot read pg_class without having selected a database");
+
+ /*
* form a scan key
*/
ScanKeyInit(&key[0],
/*
* Open pg_class and fetch a tuple. Force heap scan if we haven't yet
- * built the critical relcache entries (this includes initdb and
- * startup without a pg_internal.init file). The caller can also
- * force a heap scan by setting indexOK == false.
+ * built the critical relcache entries (this includes initdb and startup
+ * without a pg_internal.init file). The caller can also force a heap
+ * scan by setting indexOK == false.
*/
pg_class_desc = heap_open(RelationRelationId, AccessShareLock);
pg_class_scan = systable_beginscan(pg_class_desc, ClassOidIndexId,
* AllocateRelationDesc
*
* This is used to allocate memory for a new relation descriptor
- * and initialize the rd_rel field.
- *
- * If 'relation' is NULL, allocate a new RelationData object.
- * If not, reuse the given object (that path is taken only when
- * we have to rebuild a relcache entry during RelationClearRelation).
+ * and initialize the rd_rel field from the given pg_class tuple.
*/
static Relation
-AllocateRelationDesc(Relation relation, Form_pg_class relp)
+AllocateRelationDesc(Form_pg_class relp)
{
+ Relation relation;
MemoryContext oldcxt;
Form_pg_class relationForm;
oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
/*
- * allocate space for new relation descriptor, if needed
- */
- if (relation == NULL)
- relation = (Relation) palloc(sizeof(RelationData));
-
- /*
- * clear all fields of reldesc
+ * allocate and zero space for new relation descriptor
*/
- MemSet(relation, 0, sizeof(RelationData));
- relation->rd_targblock = InvalidBlockNumber;
+ relation = (Relation) palloc0(sizeof(RelationData));
/* make sure relation is marked as having no open file yet */
relation->rd_smgr = NULL;
/*
* Copy the relation tuple form
*
- * We only allocate space for the fixed fields, ie, CLASS_TUPLE_SIZE.
- * relacl is NOT stored in the relcache --- there'd be little point in
- * it, since we don't copy the tuple's nullvalues bitmap and hence
- * wouldn't know if the value is valid ... bottom line is that relacl
- * *cannot* be retrieved from the relcache. Get it from the syscache
- * if you need it.
+ * We only allocate space for the fixed fields, ie, CLASS_TUPLE_SIZE. The
+ * variable-length fields (relacl, reloptions) are NOT stored in the
+ * relcache --- there'd be little point in it, since we don't copy the
+ * tuple's nulls bitmap and hence wouldn't know if the values are valid.
+ * Bottom line is that relacl *cannot* be retrieved from the relcache. Get
+ * it from the syscache if you need it. The same goes for the original
+ * form of reloptions (however, we do store the parsed form of reloptions
+ * in rd_options).
*/
relationForm = (Form_pg_class) palloc(CLASS_TUPLE_SIZE);
/* and allocate attribute tuple form storage */
relation->rd_att = CreateTemplateTupleDesc(relationForm->relnatts,
relationForm->relhasoids);
+ /* which we mark as a reference-counted tupdesc */
+ relation->rd_att->tdrefcount = 1;
MemoryContextSwitchTo(oldcxt);
}
/*
+ * RelationParseRelOptions
+ * Convert pg_class.reloptions into pre-parsed rd_options
+ *
+ * tuple is the real pg_class tuple (not rd_rel!) for relation
+ *
+ * Note: rd_rel and (if an index) rd_am must be valid already
+ */
+static void
+RelationParseRelOptions(Relation relation, HeapTuple tuple)
+{
+ bytea *options;
+
+ relation->rd_options = NULL;
+
+ /* Fall out if relkind should not have options */
+ switch (relation->rd_rel->relkind)
+ {
+ case RELKIND_RELATION:
+ case RELKIND_TOASTVALUE:
+ case RELKIND_INDEX:
+ break;
+ default:
+ return;
+ }
+
+ /*
+ * Fetch reloptions from tuple; have to use a hardwired descriptor because
+ * we might not have any other for pg_class yet (consider executing this
+ * code for pg_class itself)
+ */
+ options = extractRelOptions(tuple,
+ GetPgClassDescriptor(),
+ relation->rd_rel->relkind == RELKIND_INDEX ?
+ relation->rd_am->amoptions : InvalidOid);
+
+ /*
+ * Copy parsed data into CacheMemoryContext. To guard against the
+ * possibility of leaks in the reloptions code, we want to do the actual
+ * parsing in the caller's memory context and copy the results into
+ * CacheMemoryContext after the fact.
+ */
+ if (options)
+ {
+ relation->rd_options = MemoryContextAlloc(CacheMemoryContext,
+ VARSIZE(options));
+ memcpy(relation->rd_options, options, VARSIZE(options));
+ pfree(options);
+ }
+}
+
+/*
* RelationBuildTupleDesc
*
* Form the relation's tuple descriptor from information in
/*
* Form a scan key that selects only user attributes (attnum > 0).
- * (Eliminating system attribute rows at the index level is lots
- * faster than fetching them.)
+ * (Eliminating system attribute rows at the index level is lots faster
+ * than fetching them.)
*/
ScanKeyInit(&skey[0],
Anum_pg_attribute_attrelid,
Int16GetDatum(0));
/*
- * Open pg_attribute and begin a scan. Force heap scan if we haven't
- * yet built the critical relcache entries (this includes initdb and
- * startup without a pg_internal.init file).
+ * Open pg_attribute and begin a scan. Force heap scan if we haven't yet
+ * built the critical relcache entries (this includes initdb and startup
+ * without a pg_internal.init file).
*/
pg_attribute_desc = heap_open(AttributeRelationId, AccessShareLock);
pg_attribute_scan = systable_beginscan(pg_attribute_desc,
memcpy(relation->rd_att->attrs[attp->attnum - 1],
attp,
- ATTRIBUTE_TUPLE_SIZE);
+ ATTRIBUTE_FIXED_PART_SIZE);
/* Update constraint/default info */
if (attp->attnotnull)
/*
* However, we can easily set the attcacheoff value for the first
- * attribute: it must be zero. This eliminates the need for special
- * cases for attnum=1 that used to exist in fastgetattr() and
- * index_getattr().
+ * attribute: it must be zero. This eliminates the need for special cases
+ * for attnum=1 that used to exist in fastgetattr() and index_getattr().
*/
if (relation->rd_rel->relnatts > 0)
relation->rd_att->attrs[0]->attcacheoff = 0;
constr->num_check = relation->rd_rel->relchecks;
constr->check = (ConstrCheck *)
MemoryContextAllocZero(CacheMemoryContext,
- constr->num_check * sizeof(ConstrCheck));
+ constr->num_check * sizeof(ConstrCheck));
CheckConstraintFetch(relation);
}
else
int maxlocks;
/*
- * Make the private context. Parameters are set on the assumption
- * that it'll probably not contain much data.
+ * Make the private context. Parameters are set on the assumption that
+ * it'll probably not contain much data.
*/
rulescxt = AllocSetContextCreate(CacheMemoryContext,
RelationGetRelationName(relation),
relation->rd_rulescxt = rulescxt;
/*
- * allocate an array to hold the rewrite rules (the array is extended
- * if necessary)
+ * allocate an array to hold the rewrite rules (the array is extended if
+ * necessary)
*/
maxlocks = 4;
rules = (RewriteRule **)
/*
* open pg_rewrite and begin a scan
*
- * Note: since we scan the rules using RewriteRelRulenameIndexId,
- * we will be reading the rules in name order, except possibly during
- * emergency-recovery operations (ie, IsIgnoringSystemIndexes). This
- * in turn ensures that rules will be fired in name order.
+ * Note: since we scan the rules using RewriteRelRulenameIndexId, we will
+ * be reading the rules in name order, except possibly during
+ * emergency-recovery operations (ie, IgnoreSystemIndexes). This in turn
+ * ensures that rules will be fired in name order.
*/
rewrite_desc = heap_open(RewriteRelationId, AccessShareLock);
rewrite_tupdesc = RelationGetDescr(rewrite_desc);
{
Form_pg_rewrite rewrite_form = (Form_pg_rewrite) GETSTRUCT(rewrite_tuple);
bool isnull;
- Datum ruleaction;
- Datum rule_evqual;
- char *ruleaction_str;
- char *rule_evqual_str;
+ Datum rule_datum;
+ char *rule_str;
RewriteRule *rule;
rule = (RewriteRule *) MemoryContextAlloc(rulescxt,
rule->event = rewrite_form->ev_type - '0';
rule->attrno = rewrite_form->ev_attr;
+ rule->enabled = rewrite_form->ev_enabled;
rule->isInstead = rewrite_form->is_instead;
- /* Must use heap_getattr to fetch ev_qual and ev_action */
-
- ruleaction = heap_getattr(rewrite_tuple,
+ /*
+ * Must use heap_getattr to fetch ev_action and ev_qual. Also, the
+ * rule strings are often large enough to be toasted. To avoid
+ * leaking memory in the caller's context, do the detoasting here so
+ * we can free the detoasted version.
+ */
+ rule_datum = heap_getattr(rewrite_tuple,
Anum_pg_rewrite_ev_action,
rewrite_tupdesc,
&isnull);
Assert(!isnull);
- ruleaction_str = DatumGetCString(DirectFunctionCall1(textout,
- ruleaction));
+ rule_str = TextDatumGetCString(rule_datum);
oldcxt = MemoryContextSwitchTo(rulescxt);
- rule->actions = (List *) stringToNode(ruleaction_str);
+ rule->actions = (List *) stringToNode(rule_str);
MemoryContextSwitchTo(oldcxt);
- pfree(ruleaction_str);
+ pfree(rule_str);
- rule_evqual = heap_getattr(rewrite_tuple,
- Anum_pg_rewrite_ev_qual,
- rewrite_tupdesc,
- &isnull);
+ rule_datum = heap_getattr(rewrite_tuple,
+ Anum_pg_rewrite_ev_qual,
+ rewrite_tupdesc,
+ &isnull);
Assert(!isnull);
- rule_evqual_str = DatumGetCString(DirectFunctionCall1(textout,
- rule_evqual));
+ rule_str = TextDatumGetCString(rule_datum);
oldcxt = MemoryContextSwitchTo(rulescxt);
- rule->qual = (Node *) stringToNode(rule_evqual_str);
+ rule->qual = (Node *) stringToNode(rule_str);
MemoryContextSwitchTo(oldcxt);
- pfree(rule_evqual_str);
+ pfree(rule_str);
+
+ /*
+ * We want the rule's table references to be checked as though by the
+ * table owner, not the user referencing the rule. Therefore, scan
+ * through the rule's actions and set the checkAsUser field on all
+ * rtable entries. We have to look at the qual as well, in case it
+ * contains sublinks.
+ *
+ * The reason for doing this when the rule is loaded, rather than when
+ * it is stored, is that otherwise ALTER TABLE OWNER would have to
+ * grovel through stored rules to update checkAsUser fields. Scanning
+ * the rule tree during load is relatively cheap (compared to
+ * constructing it in the first place), so we do it here.
+ */
+ setRuleCheckAsUser((Node *) rule->actions, relation->rd_rel->relowner);
+ setRuleCheckAsUser(rule->qual, relation->rd_rel->relowner);
if (numlocks >= maxlocks)
{
heap_close(rewrite_desc, AccessShareLock);
/*
+ * there might not be any rules (if relhasrules is out-of-date)
+ */
+ if (numlocks == 0)
+ {
+ relation->rd_rules = NULL;
+ relation->rd_rulescxt = NULL;
+ MemoryContextDelete(rulescxt);
+ return;
+ }
+
+ /*
* form a RuleLock and insert into relation
*/
rulelock = (RuleLock *) MemoryContextAlloc(rulescxt, sizeof(RuleLock));
/*
* As of 7.3 we assume the rule ordering is repeatable, because
- * RelationBuildRuleLock should read 'em in a consistent order. So
- * just compare corresponding slots.
+ * RelationBuildRuleLock should read 'em in a consistent order. So just
+ * compare corresponding slots.
*/
if (rlock1 != NULL)
{
return false;
if (rule1->attrno != rule2->attrno)
return false;
+ if (rule1->enabled != rule2->enabled)
+ return false;
if (rule1->isInstead != rule2->isInstead)
return false;
if (!equal(rule1->qual, rule2->qual))
}
-/* ----------------------------------
+/*
* RelationBuildDesc
*
- * Build a relation descriptor --- either a new one, or by
- * recycling the given old relation object. The latter case
- * supports rebuilding a relcache entry without invalidating
- * pointers to it.
- * --------------------------------
+ * Build a relation descriptor. The caller must hold at least
+ * AccessShareLock on the target relid.
+ *
+ * The new descriptor is inserted into the hash table if insertIt is true.
+ *
+ * Returns NULL if no pg_class row could be found for the given relid
+ * (suggesting we are trying to access a just-deleted relation).
+ * Any other error is reported via elog.
*/
static Relation
-RelationBuildDesc(Oid targetRelId, Relation oldrelation)
+RelationBuildDesc(Oid targetRelId, bool insertIt)
{
Relation relation;
Oid relid;
HeapTuple pg_class_tuple;
Form_pg_class relp;
- MemoryContext oldcxt;
/*
* find the tuple in pg_class corresponding to the given relation id
*/
relid = HeapTupleGetOid(pg_class_tuple);
relp = (Form_pg_class) GETSTRUCT(pg_class_tuple);
+ Assert(relid == targetRelId);
/*
- * allocate storage for the relation descriptor, and copy
- * pg_class_tuple to relation->rd_rel.
- */
- relation = AllocateRelationDesc(oldrelation, relp);
-
- /*
- * now we can free the memory allocated for pg_class_tuple
+ * allocate storage for the relation descriptor, and copy pg_class_tuple
+ * to relation->rd_rel.
*/
- heap_freetuple(pg_class_tuple);
+ relation = AllocateRelationDesc(relp);
/*
* initialize the relation's relation id (relation->rd_id)
RelationGetRelid(relation) = relid;
/*
- * normal relations are not nailed into the cache; nor can a
- * pre-existing relation be new. It could be temp though. (Actually,
- * it could be new too, but it's okay to forget that fact if forced to
- * flush the entry.)
+ * normal relations are not nailed into the cache; nor can a pre-existing
+ * relation be new. It could be temp though. (Actually, it could be new
+ * too, but it's okay to forget that fact if forced to flush the entry.)
*/
relation->rd_refcnt = 0;
relation->rd_isnailed = false;
relation->rd_createSubid = InvalidSubTransactionId;
- relation->rd_istemp = isTempNamespace(relation->rd_rel->relnamespace);
+ relation->rd_newRelfilenodeSubid = InvalidSubTransactionId;
+ switch (relation->rd_rel->relpersistence)
+ {
+ case RELPERSISTENCE_PERMANENT:
+ relation->rd_backend = InvalidBackendId;
+ break;
+ case RELPERSISTENCE_TEMP:
+ if (isTempOrToastNamespace(relation->rd_rel->relnamespace))
+ relation->rd_backend = MyBackendId;
+ else
+ {
+ /*
+ * If it's a local temp table, but not one of ours, we have to
+ * use the slow, grotty method to figure out the owning
+ * backend.
+ */
+ relation->rd_backend =
+ GetTempNamespaceBackendId(relation->rd_rel->relnamespace);
+ Assert(relation->rd_backend != InvalidBackendId);
+ }
+ break;
+ default:
+ elog(ERROR, "invalid relpersistence: %c",
+ relation->rd_rel->relpersistence);
+ break;
+ }
/*
* initialize the tuple descriptor (relation->rd_att).
relation->rd_rulescxt = NULL;
}
- if (relation->rd_rel->reltriggers > 0)
+ if (relation->rd_rel->relhastriggers)
RelationBuildTriggers(relation);
else
relation->trigdesc = NULL;
if (OidIsValid(relation->rd_rel->relam))
RelationInitIndexAccessInfo(relation);
+ /* extract reloptions if any */
+ RelationParseRelOptions(relation, pg_class_tuple);
+
/*
* initialize the relation lock manager information
*/
relation->rd_smgr = NULL;
/*
- * Insert newly created relation into relcache hash tables.
+ * now we can free the memory allocated for pg_class_tuple
*/
- oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
- RelationCacheInsert(relation);
- MemoryContextSwitchTo(oldcxt);
+ heap_freetuple(pg_class_tuple);
+
+ /*
+ * Insert newly created relation into relcache hash table, if requested.
+ */
+ if (insertIt)
+ RelationCacheInsert(relation);
/* It's fully valid */
relation->rd_isvalid = true;
/*
* Initialize the physical addressing info (RelFileNode) for a relcache entry
+ *
+ * Note: at the physical level, relations in the pg_global tablespace must
+ * be treated as shared, even if relisshared isn't set. Hence we do not
+ * look at relisshared here.
*/
static void
RelationInitPhysicalAddr(Relation relation)
relation->rd_node.spcNode = relation->rd_rel->reltablespace;
else
relation->rd_node.spcNode = MyDatabaseTableSpace;
- if (relation->rd_rel->relisshared)
+ if (relation->rd_node.spcNode == GLOBALTABLESPACE_OID)
relation->rd_node.dbNode = InvalidOid;
else
relation->rd_node.dbNode = MyDatabaseId;
- relation->rd_node.relNode = relation->rd_rel->relfilenode;
+ if (relation->rd_rel->relfilenode)
+ relation->rd_node.relNode = relation->rd_rel->relfilenode;
+ else
+ {
+ /* Consult the relation mapper */
+ relation->rd_node.relNode =
+ RelationMapOidToFilenode(relation->rd_id,
+ relation->rd_rel->relisshared);
+ if (!OidIsValid(relation->rd_node.relNode))
+ elog(ERROR, "could not find relation mapping for relation \"%s\", OID %u",
+ RelationGetRelationName(relation), relation->rd_id);
+ }
}
/*
HeapTuple tuple;
Form_pg_am aform;
Datum indclassDatum;
+ Datum indoptionDatum;
bool isnull;
+ oidvector *indclass;
+ int2vector *indoption;
MemoryContext indexcxt;
MemoryContext oldcontext;
- Oid *operator;
- RegProcedure *support;
- FmgrInfo *supportinfo;
int natts;
- uint16 amstrategies;
uint16 amsupport;
/*
* Make a copy of the pg_index entry for the index. Since pg_index
- * contains variable-length and possibly-null fields, we have to do
- * this honestly rather than just treating it as a Form_pg_index
- * struct.
+ * contains variable-length and possibly-null fields, we have to do this
+ * honestly rather than just treating it as a Form_pg_index struct.
*/
- tuple = SearchSysCache(INDEXRELID,
- ObjectIdGetDatum(RelationGetRelid(relation)),
- 0, 0, 0);
+ tuple = SearchSysCache1(INDEXRELID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for index %u",
RelationGetRelid(relation));
ReleaseSysCache(tuple);
/*
- * indclass cannot be referenced directly through the C struct, because
- * it is after the variable-width indkey field. Therefore we extract
- * the datum the hard way and provide a direct link in the relcache.
- */
- indclassDatum = fastgetattr(relation->rd_indextuple,
- Anum_pg_index_indclass,
- GetPgIndexDescriptor(),
- &isnull);
- Assert(!isnull);
- relation->rd_indclass = (oidvector *) DatumGetPointer(indclassDatum);
-
- /*
* Make a copy of the pg_am entry for the index's access method
*/
- tuple = SearchSysCache(AMOID,
- ObjectIdGetDatum(relation->rd_rel->relam),
- 0, 0, 0);
+ tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(relation->rd_rel->relam));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for access method %u",
relation->rd_rel->relam);
if (natts != relation->rd_index->indnatts)
elog(ERROR, "relnatts disagrees with indnatts for index %u",
RelationGetRelid(relation));
- amstrategies = aform->amstrategies;
amsupport = aform->amsupport;
/*
- * Make the private context to hold index access info. The reason we
- * need a context, and not just a couple of pallocs, is so that we
- * won't leak any subsidiary info attached to fmgr lookup records.
+ * Make the private context to hold index access info. The reason we need
+ * a context, and not just a couple of pallocs, is so that we won't leak
+ * any subsidiary info attached to fmgr lookup records.
*
* Context parameters are set on the assumption that it'll probably not
* contain much data.
relation->rd_aminfo = (RelationAmInfo *)
MemoryContextAllocZero(indexcxt, sizeof(RelationAmInfo));
- if (amstrategies > 0)
- operator = (Oid *)
- MemoryContextAllocZero(indexcxt,
- natts * amstrategies * sizeof(Oid));
- else
- operator = NULL;
+ relation->rd_opfamily = (Oid *)
+ MemoryContextAllocZero(indexcxt, natts * sizeof(Oid));
+ relation->rd_opcintype = (Oid *)
+ MemoryContextAllocZero(indexcxt, natts * sizeof(Oid));
if (amsupport > 0)
{
int nsupport = natts * amsupport;
- support = (RegProcedure *)
+ relation->rd_support = (RegProcedure *)
MemoryContextAllocZero(indexcxt, nsupport * sizeof(RegProcedure));
- supportinfo = (FmgrInfo *)
+ relation->rd_supportinfo = (FmgrInfo *)
MemoryContextAllocZero(indexcxt, nsupport * sizeof(FmgrInfo));
}
else
{
- support = NULL;
- supportinfo = NULL;
+ relation->rd_support = NULL;
+ relation->rd_supportinfo = NULL;
}
- relation->rd_operator = operator;
- relation->rd_support = support;
- relation->rd_supportinfo = supportinfo;
+ relation->rd_indoption = (int16 *)
+ MemoryContextAllocZero(indexcxt, natts * sizeof(int16));
+
+ /*
+ * indclass cannot be referenced directly through the C struct, because it
+ * comes after the variable-width indkey field. Must extract the datum
+ * the hard way...
+ */
+ indclassDatum = fastgetattr(relation->rd_indextuple,
+ Anum_pg_index_indclass,
+ GetPgIndexDescriptor(),
+ &isnull);
+ Assert(!isnull);
+ indclass = (oidvector *) DatumGetPointer(indclassDatum);
+
+ /*
+ * Fill the support procedure OID array, as well as the info about
+ * opfamilies and opclass input types. (aminfo and supportinfo are left
+ * as zeroes, and are filled on-the-fly when used)
+ */
+ IndexSupportInitialize(indclass, relation->rd_support,
+ relation->rd_opfamily, relation->rd_opcintype,
+ amsupport, natts);
/*
- * Fill the operator and support procedure OID arrays. (aminfo and
- * supportinfo are left as zeroes, and are filled on-the-fly when used)
+ * Similarly extract indoption and copy it to the cache entry
*/
- IndexSupportInitialize(relation->rd_indclass,
- operator, support,
- amstrategies, amsupport, natts);
+ indoptionDatum = fastgetattr(relation->rd_indextuple,
+ Anum_pg_index_indoption,
+ GetPgIndexDescriptor(),
+ &isnull);
+ Assert(!isnull);
+ indoption = (int2vector *) DatumGetPointer(indoptionDatum);
+ memcpy(relation->rd_indoption, indoption->values, natts * sizeof(int16));
/*
- * expressions and predicate cache will be filled later
+ * expressions, predicate, exclusion caches will be filled later
*/
relation->rd_indexprs = NIL;
relation->rd_indpred = NIL;
+ relation->rd_exclops = NULL;
+ relation->rd_exclprocs = NULL;
+ relation->rd_exclstrats = NULL;
+ relation->rd_amcache = NULL;
}
/*
* Initializes an index's cached opclass information,
* given the index's pg_index.indclass entry.
*
- * Data is returned into *indexOperator and *indexSupport, which are arrays
- * allocated by the caller.
+ * Data is returned into *indexSupport, *opFamily, and *opcInType,
+ * which are arrays allocated by the caller.
*
- * The caller also passes maxStrategyNumber, maxSupportNumber, and
- * maxAttributeNumber, since these indicate the size of the arrays
- * it has allocated --- but in practice these numbers must always match
- * those obtainable from the system catalog entries for the index and
- * access method.
+ * The caller also passes maxSupportNumber and maxAttributeNumber, since these
+ * indicate the size of the arrays it has allocated --- but in practice these
+ * numbers must always match those obtainable from the system catalog entries
+ * for the index and access method.
*/
static void
IndexSupportInitialize(oidvector *indclass,
- Oid *indexOperator,
RegProcedure *indexSupport,
- StrategyNumber maxStrategyNumber,
+ Oid *opFamily,
+ Oid *opcInType,
StrategyNumber maxSupportNumber,
AttrNumber maxAttributeNumber)
{
/* look up the info for this opclass, using a cache */
opcentry = LookupOpclassInfo(indclass->values[attIndex],
- maxStrategyNumber,
maxSupportNumber);
/* copy cached data into relcache entry */
- if (maxStrategyNumber > 0)
- memcpy(&indexOperator[attIndex * maxStrategyNumber],
- opcentry->operatorOids,
- maxStrategyNumber * sizeof(Oid));
+ opFamily[attIndex] = opcentry->opcfamily;
+ opcInType[attIndex] = opcentry->opcintype;
if (maxSupportNumber > 0)
memcpy(&indexSupport[attIndex * maxSupportNumber],
opcentry->supportProcs,
* This routine maintains a per-opclass cache of the information needed
* by IndexSupportInitialize(). This is more efficient than relying on
* the catalog cache, because we can load all the info about a particular
- * opclass in a single indexscan of pg_amproc or pg_amop.
+ * opclass in a single indexscan of pg_amproc.
*
- * The information from pg_am about expected range of strategy and support
+ * The information from pg_am about expected range of support function
* numbers is passed in, rather than being looked up, mainly because the
* caller will have it already.
*
- * XXX There isn't any provision for flushing the cache. However, there
- * isn't any provision for flushing relcache entries when opclass info
- * changes, either :-(
+ * Note there is no provision for flushing the cache. This is OK at the
+ * moment because there is no way to ALTER any interesting properties of an
+ * existing opclass --- all you can do is drop it, which will result in
+ * a useless but harmless dead entry in the cache. To support altering
+ * opclass membership (not the same as opfamily membership!), we'd need to
+ * be able to flush this cache as well as the contents of relcache entries
+ * for indexes.
*/
static OpClassCacheEnt *
LookupOpclassInfo(Oid operatorClassOid,
- StrategyNumber numStrats,
StrategyNumber numSupport)
{
OpClassCacheEnt *opcentry;
bool found;
Relation rel;
SysScanDesc scan;
- ScanKeyData skey[2];
+ ScanKeyData skey[3];
HeapTuple htup;
bool indexOK;
/* First time through: initialize the opclass cache */
HASHCTL ctl;
- if (!CacheMemoryContext)
- CreateCacheMemoryContext();
-
MemSet(&ctl, 0, sizeof(ctl));
ctl.keysize = sizeof(Oid);
ctl.entrysize = sizeof(OpClassCacheEnt);
ctl.hash = oid_hash;
OpClassCache = hash_create("Operator class cache", 64,
&ctl, HASH_ELEM | HASH_FUNCTION);
+
+ /* Also make sure CacheMemoryContext exists */
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
}
opcentry = (OpClassCacheEnt *) hash_search(OpClassCache,
(void *) &operatorClassOid,
HASH_ENTER, &found);
- if (found && opcentry->valid)
+ if (!found)
+ {
+ /* Need to allocate memory for new entry */
+ opcentry->valid = false; /* until known OK */
+ opcentry->numSupport = numSupport;
+
+ if (numSupport > 0)
+ opcentry->supportProcs = (RegProcedure *)
+ MemoryContextAllocZero(CacheMemoryContext,
+ numSupport * sizeof(RegProcedure));
+ else
+ opcentry->supportProcs = NULL;
+ }
+ else
{
- /* Already made an entry for it */
- Assert(numStrats == opcentry->numStrats);
Assert(numSupport == opcentry->numSupport);
- return opcentry;
}
- /* Need to fill in new entry */
- opcentry->valid = false; /* until known OK */
- opcentry->numStrats = numStrats;
- opcentry->numSupport = numSupport;
-
- if (numStrats > 0)
- opcentry->operatorOids = (Oid *)
- MemoryContextAllocZero(CacheMemoryContext,
- numStrats * sizeof(Oid));
- else
- opcentry->operatorOids = NULL;
+ /*
+ * When testing for cache-flush hazards, we intentionally disable the
+ * operator class cache and force reloading of the info on each call. This
+ * is helpful because we want to test the case where a cache flush occurs
+ * while we are loading the info, and it's very hard to provoke that if
+ * this happens only once per opclass per backend.
+ */
+#if defined(CLOBBER_CACHE_ALWAYS)
+ opcentry->valid = false;
+#endif
- if (numSupport > 0)
- opcentry->supportProcs = (RegProcedure *)
- MemoryContextAllocZero(CacheMemoryContext,
- numSupport * sizeof(RegProcedure));
- else
- opcentry->supportProcs = NULL;
+ if (opcentry->valid)
+ return opcentry;
/*
- * To avoid infinite recursion during startup, force heap scans if
- * we're looking up info for the opclasses used by the indexes we
- * would like to reference here.
+ * Need to fill in new entry.
+ *
+ * To avoid infinite recursion during startup, force heap scans if we're
+ * looking up info for the opclasses used by the indexes we would like to
+ * reference here.
*/
indexOK = criticalRelcachesBuilt ||
(operatorClassOid != OID_BTREE_OPS_OID &&
operatorClassOid != INT2_BTREE_OPS_OID);
/*
- * Scan pg_amop to obtain operators for the opclass. We only fetch
- * the default ones (those with subtype zero).
+ * We have to fetch the pg_opclass row to determine its opfamily and
+ * opcintype, which are needed to look up related operators and functions.
+ * It'd be convenient to use the syscache here, but that probably doesn't
+ * work while bootstrapping.
*/
- if (numStrats > 0)
- {
- ScanKeyInit(&skey[0],
- Anum_pg_amop_amopclaid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(operatorClassOid));
- ScanKeyInit(&skey[1],
- Anum_pg_amop_amopsubtype,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(InvalidOid));
- rel = heap_open(AccessMethodOperatorRelationId, AccessShareLock);
- scan = systable_beginscan(rel, AccessMethodStrategyIndexId, indexOK,
- SnapshotNow, 2, skey);
+ ScanKeyInit(&skey[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(operatorClassOid));
+ rel = heap_open(OperatorClassRelationId, AccessShareLock);
+ scan = systable_beginscan(rel, OpclassOidIndexId, indexOK,
+ SnapshotNow, 1, skey);
- while (HeapTupleIsValid(htup = systable_getnext(scan)))
- {
- Form_pg_amop amopform = (Form_pg_amop) GETSTRUCT(htup);
-
- if (amopform->amopstrategy <= 0 ||
- (StrategyNumber) amopform->amopstrategy > numStrats)
- elog(ERROR, "invalid amopstrategy number %d for opclass %u",
- amopform->amopstrategy, operatorClassOid);
- opcentry->operatorOids[amopform->amopstrategy - 1] =
- amopform->amopopr;
- }
+ if (HeapTupleIsValid(htup = systable_getnext(scan)))
+ {
+ Form_pg_opclass opclassform = (Form_pg_opclass) GETSTRUCT(htup);
- systable_endscan(scan);
- heap_close(rel, AccessShareLock);
+ opcentry->opcfamily = opclassform->opcfamily;
+ opcentry->opcintype = opclassform->opcintype;
}
+ else
+ elog(ERROR, "could not find tuple for opclass %u", operatorClassOid);
+
+ systable_endscan(scan);
+ heap_close(rel, AccessShareLock);
/*
- * Scan pg_amproc to obtain support procs for the opclass. We only
- * fetch the default ones (those with subtype zero).
+ * Scan pg_amproc to obtain support procs for the opclass. We only fetch
+ * the default ones (those with lefttype = righttype = opcintype).
*/
if (numSupport > 0)
{
ScanKeyInit(&skey[0],
- Anum_pg_amproc_amopclaid,
+ Anum_pg_amproc_amprocfamily,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(operatorClassOid));
+ ObjectIdGetDatum(opcentry->opcfamily));
ScanKeyInit(&skey[1],
- Anum_pg_amproc_amprocsubtype,
+ Anum_pg_amproc_amproclefttype,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(opcentry->opcintype));
+ ScanKeyInit(&skey[2],
+ Anum_pg_amproc_amprocrighttype,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(InvalidOid));
+ ObjectIdGetDatum(opcentry->opcintype));
rel = heap_open(AccessMethodProcedureRelationId, AccessShareLock);
scan = systable_beginscan(rel, AccessMethodProcedureIndexId, indexOK,
- SnapshotNow, 2, skey);
+ SnapshotNow, 3, skey);
while (HeapTupleIsValid(htup = systable_getnext(scan)))
{
/*
* formrdesc
*
- * This is a special cut-down version of RelationBuildDesc()
- * used by RelationCacheInitialize() in initializing the relcache.
+ * This is a special cut-down version of RelationBuildDesc(),
+ * used while initializing the relcache.
* The relation descriptor is built just from the supplied parameters,
* without actually looking at any system table entries. We cheat
* quite a lot since we only need to work for a few basic system
* catalogs.
*
- * formrdesc is currently used for: pg_class, pg_attribute, pg_proc,
- * and pg_type (see RelationCacheInitialize).
+ * formrdesc is currently used for: pg_database, pg_authid, pg_auth_members,
+ * pg_class, pg_attribute, pg_proc, and pg_type
+ * (see RelationCacheInitializePhase2/3).
*
* Note that these catalogs can't have constraints (except attnotnull),
* default values, rules, or triggers, since we don't cope with any of that.
+ * (Well, actually, this only matters for properties that need to be valid
+ * during bootstrap or before RelationCacheInitializePhase3 runs, and none of
+ * these properties matter then...)
*
* NOTE: we assume we are already switched into CacheMemoryContext.
*/
static void
formrdesc(const char *relationName, Oid relationReltype,
- bool hasoids, int natts, FormData_pg_attribute *att)
+ bool isshared, bool hasoids,
+ int natts, const FormData_pg_attribute *attrs)
{
Relation relation;
int i;
* allocate new relation desc, clear all fields of reldesc
*/
relation = (Relation) palloc0(sizeof(RelationData));
- relation->rd_targblock = InvalidBlockNumber;
/* make sure relation is marked as having no open file yet */
relation->rd_smgr = NULL;
relation->rd_refcnt = 1;
/*
- * all entries built with this routine are nailed-in-cache; none are
- * for new or temp relations.
+ * all entries built with this routine are nailed-in-cache; none are for
+ * new or temp relations.
*/
relation->rd_isnailed = true;
relation->rd_createSubid = InvalidSubTransactionId;
- relation->rd_istemp = false;
+ relation->rd_newRelfilenodeSubid = InvalidSubTransactionId;
+ relation->rd_backend = InvalidBackendId;
/*
* initialize relation tuple form
*
* The data we insert here is pretty incomplete/bogus, but it'll serve to
- * get us launched. RelationCacheInitializePhase2() will read the
- * real data from pg_class and replace what we've done here.
+ * get us launched. RelationCacheInitializePhase3() will read the real
+ * data from pg_class and replace what we've done here. Note in
+ * particular that relowner is left as zero; this cues
+ * RelationCacheInitializePhase3 that the real data isn't there yet.
*/
relation->rd_rel = (Form_pg_class) palloc0(CLASS_TUPLE_SIZE);
relation->rd_rel->reltype = relationReltype;
/*
- * It's important to distinguish between shared and non-shared
- * relations, even at bootstrap time, to make sure we know where they
- * are stored. At present, all relations that formrdesc is used for
- * are not shared.
+ * It's important to distinguish between shared and non-shared relations,
+ * even at bootstrap time, to make sure we know where they are stored.
*/
- relation->rd_rel->relisshared = false;
+ relation->rd_rel->relisshared = isshared;
+ if (isshared)
+ relation->rd_rel->reltablespace = GLOBALTABLESPACE_OID;
+
+ /* formrdesc is used only for permanent relations */
+ relation->rd_rel->relpersistence = RELPERSISTENCE_PERMANENT;
relation->rd_rel->relpages = 1;
relation->rd_rel->reltuples = 1;
* initialize attribute tuple form
*
* Unlike the case with the relation tuple, this data had better be right
- * because it will never be replaced. The input values must be
- * correctly defined by macros in src/include/catalog/ headers.
+ * because it will never be replaced. The data comes from
+ * src/include/catalog/ headers via genbki.pl.
*/
relation->rd_att = CreateTemplateTupleDesc(natts, hasoids);
+ relation->rd_att->tdrefcount = 1; /* mark as refcounted */
+
relation->rd_att->tdtypeid = relationReltype;
relation->rd_att->tdtypmod = -1; /* unnecessary, but... */
for (i = 0; i < natts; i++)
{
memcpy(relation->rd_att->attrs[i],
- &att[i],
- ATTRIBUTE_TUPLE_SIZE);
- has_not_null |= att[i].attnotnull;
+ &attrs[i],
+ ATTRIBUTE_FIXED_PART_SIZE);
+ has_not_null |= attrs[i].attnotnull;
/* make sure attcacheoff is valid */
relation->rd_att->attrs[i]->attcacheoff = -1;
}
* initialize relation id from info in att array (my, this is ugly)
*/
RelationGetRelid(relation) = relation->rd_att->attrs[0]->attrelid;
- relation->rd_rel->relfilenode = RelationGetRelid(relation);
+
+ /*
+ * All relations made with formrdesc are mapped. This is necessarily so
+ * because there is no other way to know what filenode they currently
+ * have. In bootstrap mode, add them to the initial relation mapper data,
+ * specifying that the initial filenode is the same as the OID.
+ */
+ relation->rd_rel->relfilenode = InvalidOid;
+ if (IsBootstrapProcessingMode())
+ RelationMapUpdateMap(RelationGetRelid(relation),
+ RelationGetRelid(relation),
+ isshared, true);
/*
* initialize the relation lock manager information
*/
/*
- * RelationIdCacheGetRelation
- *
- * Lookup an existing reldesc by OID.
- *
- * Only try to get the reldesc by looking in the cache,
- * do not go to the disk if it's not present.
- *
- * NB: relation ref count is incremented if successful.
- * Caller should eventually decrement count. (Usually,
- * that happens by calling RelationClose().)
- */
-Relation
-RelationIdCacheGetRelation(Oid relationId)
-{
- Relation rd;
-
- RelationIdCacheLookup(relationId, rd);
-
- if (RelationIsValid(rd))
- {
- RelationIncrementReferenceCount(rd);
- /* revalidate nailed index if necessary */
- if (!rd->rd_isvalid)
- RelationReloadClassinfo(rd);
- }
-
- return rd;
-}
-
-/*
* RelationIdGetRelation
*
* Lookup a reldesc by OID; make one if not already in cache.
*
+ * Returns NULL if no pg_class row could be found for the given relid
+ * (suggesting we are trying to access a just-deleted relation).
+ * Any other error is reported via elog.
+ *
+ * NB: caller should already have at least AccessShareLock on the
+ * relation ID, else there are nasty race conditions.
+ *
* NB: relation ref count is incremented, or set to 1 if new entry.
* Caller should eventually decrement count. (Usually,
* that happens by calling RelationClose().)
Relation rd;
/*
- * first try and get a reldesc from the cache
+ * first try to find reldesc in the cache
*/
- rd = RelationIdCacheGetRelation(relationId);
+ RelationIdCacheLookup(relationId, rd);
+
if (RelationIsValid(rd))
+ {
+ RelationIncrementReferenceCount(rd);
+ /* revalidate cache entry if necessary */
+ if (!rd->rd_isvalid)
+ {
+ /*
+ * Indexes only have a limited number of possible schema changes,
+ * and we don't want to use the full-blown procedure because it's
+ * a headache for indexes that reload itself depends on.
+ */
+ if (rd->rd_rel->relkind == RELKIND_INDEX)
+ RelationReloadIndexInfo(rd);
+ else
+ RelationClearRelation(rd, true);
+ }
return rd;
+ }
/*
- * no reldesc in the cache, so have RelationBuildDesc() build one and
- * add it.
+ * no reldesc in the cache, so have RelationBuildDesc() build one and add
+ * it.
*/
- rd = RelationBuildDesc(relationId, NULL);
+ rd = RelationBuildDesc(relationId, true);
if (RelationIsValid(rd))
RelationIncrementReferenceCount(rd);
return rd;
#ifdef RELCACHE_FORCE_RELEASE
if (RelationHasReferenceCountZero(relation) &&
- relation->rd_createSubid == InvalidSubTransactionId)
+ relation->rd_createSubid == InvalidSubTransactionId &&
+ relation->rd_newRelfilenodeSubid == InvalidSubTransactionId)
RelationClearRelation(relation, false);
#endif
}
/*
- * RelationReloadClassinfo - reload the pg_class row (only)
+ * RelationReloadIndexInfo - reload minimal information for an open index
*
- * This function is used only for nailed indexes. Since a REINDEX can
- * change the relfilenode value for a nailed index, we have to reread
- * the pg_class row anytime we get an SI invalidation on a nailed index
- * (without throwing away the whole relcache entry, since we'd be unable
- * to rebuild it).
+ * This function is used only for indexes. A relcache inval on an index
+ * can mean that its pg_class or pg_index row changed. There are only
+ * very limited changes that are allowed to an existing index's schema,
+ * so we can update the relcache entry without a complete rebuild; which
+ * is fortunate because we can't rebuild an index entry that is "nailed"
+ * and/or in active use. We support full replacement of the pg_class row,
+ * as well as updates of a few simple fields of the pg_index row.
*
- * We can't necessarily reread the pg_class row right away; we might be
+ * We can't necessarily reread the catalog rows right away; we might be
* in a failed transaction when we receive the SI notification. If so,
* RelationClearRelation just marks the entry as invalid by setting
* rd_isvalid to false. This routine is called to fix the entry when it
* is next needed.
+ *
+ * We assume that at the time we are called, we have at least AccessShareLock
+ * on the target index. (Note: in the calls from RelationClearRelation,
+ * this is legitimate because we know the rel has positive refcount.)
*/
static void
-RelationReloadClassinfo(Relation relation)
+RelationReloadIndexInfo(Relation relation)
{
bool indexOK;
HeapTuple pg_class_tuple;
Form_pg_class relp;
- /* Should be called only for invalidated nailed indexes */
- Assert(relation->rd_isnailed && !relation->rd_isvalid &&
- relation->rd_rel->relkind == RELKIND_INDEX);
+ /* Should be called only for invalidated indexes */
+ Assert(relation->rd_rel->relkind == RELKIND_INDEX &&
+ !relation->rd_isvalid);
+ /* Should be closed at smgr level */
+ Assert(relation->rd_smgr == NULL);
+
+ /* Must free any AM cached data upon relcache flush */
+ if (relation->rd_amcache)
+ pfree(relation->rd_amcache);
+ relation->rd_amcache = NULL;
+
+ /*
+ * If it's a shared index, we might be called before backend startup has
+ * finished selecting a database, in which case we have no way to read
+ * pg_class yet. However, a shared index can never have any significant
+ * schema updates, so it's okay to ignore the invalidation signal. Just
+ * mark it valid and return without doing anything more.
+ */
+ if (relation->rd_rel->relisshared && !criticalRelcachesBuilt)
+ {
+ relation->rd_isvalid = true;
+ return;
+ }
+
/*
* Read the pg_class row
*
- * Don't try to use an indexscan of pg_class_oid_index to reload the
- * info for pg_class_oid_index ...
+ * Don't try to use an indexscan of pg_class_oid_index to reload the info
+ * for pg_class_oid_index ...
*/
indexOK = (RelationGetRelid(relation) != ClassOidIndexId);
pg_class_tuple = ScanPgRelation(RelationGetRelid(relation), indexOK);
if (!HeapTupleIsValid(pg_class_tuple))
- elog(ERROR, "could not find tuple for system relation %u",
+ elog(ERROR, "could not find pg_class tuple for index %u",
RelationGetRelid(relation));
relp = (Form_pg_class) GETSTRUCT(pg_class_tuple);
- memcpy((char *) relation->rd_rel, (char *) relp, CLASS_TUPLE_SIZE);
- /* Now we can recalculate physical address */
- RelationInitPhysicalAddr(relation);
+ memcpy(relation->rd_rel, relp, CLASS_TUPLE_SIZE);
+ /* Reload reloptions in case they changed */
+ if (relation->rd_options)
+ pfree(relation->rd_options);
+ RelationParseRelOptions(relation, pg_class_tuple);
+ /* done with pg_class tuple */
heap_freetuple(pg_class_tuple);
- relation->rd_targblock = InvalidBlockNumber;
+ /* We must recalculate physical address in case it changed */
+ RelationInitPhysicalAddr(relation);
+
+ /*
+ * For a non-system index, there are fields of the pg_index row that are
+ * allowed to change, so re-read that row and update the relcache entry.
+ * Most of the info derived from pg_index (such as support function lookup
+ * info) cannot change, and indeed the whole point of this routine is to
+ * update the relcache entry without clobbering that data; so wholesale
+ * replacement is not appropriate.
+ */
+ if (!IsSystemRelation(relation))
+ {
+ HeapTuple tuple;
+ Form_pg_index index;
+
+ tuple = SearchSysCache1(INDEXRELID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for index %u",
+ RelationGetRelid(relation));
+ index = (Form_pg_index) GETSTRUCT(tuple);
+
+ relation->rd_index->indisvalid = index->indisvalid;
+ relation->rd_index->indcheckxmin = index->indcheckxmin;
+ relation->rd_index->indisready = index->indisready;
+ HeapTupleHeaderSetXmin(relation->rd_indextuple->t_data,
+ HeapTupleHeaderGetXmin(tuple->t_data));
+
+ ReleaseSysCache(tuple);
+ }
+
/* Okay, now it's valid again */
relation->rd_isvalid = true;
}
/*
+ * RelationDestroyRelation
+ *
+ * Physically delete a relation cache entry and all subsidiary data.
+ * Caller must already have unhooked the entry from the hash table.
+ */
+static void
+RelationDestroyRelation(Relation relation)
+{
+ Assert(RelationHasReferenceCountZero(relation));
+
+ /*
+ * Make sure smgr and lower levels close the relation's files, if they
+ * weren't closed already. (This was probably done by caller, but let's
+ * just be real sure.)
+ */
+ RelationCloseSmgr(relation);
+
+ /*
+ * Free all the subsidiary data structures of the relcache entry, then the
+ * entry itself.
+ */
+ if (relation->rd_rel)
+ pfree(relation->rd_rel);
+ /* can't use DecrTupleDescRefCount here */
+ Assert(relation->rd_att->tdrefcount > 0);
+ if (--relation->rd_att->tdrefcount == 0)
+ FreeTupleDesc(relation->rd_att);
+ list_free(relation->rd_indexlist);
+ bms_free(relation->rd_indexattr);
+ FreeTriggerDesc(relation->trigdesc);
+ if (relation->rd_options)
+ pfree(relation->rd_options);
+ if (relation->rd_indextuple)
+ pfree(relation->rd_indextuple);
+ if (relation->rd_am)
+ pfree(relation->rd_am);
+ if (relation->rd_indexcxt)
+ MemoryContextDelete(relation->rd_indexcxt);
+ if (relation->rd_rulescxt)
+ MemoryContextDelete(relation->rd_rulescxt);
+ pfree(relation);
+}
+
+/*
* RelationClearRelation
*
* Physically blow away a relation cache entry, or reset it and rebuild
* it from scratch (that is, from catalog entries). The latter path is
- * usually used when we are notified of a change to an open relation
- * (one with refcount > 0). However, this routine just does whichever
- * it's told to do; callers must determine which they want.
+ * used when we are notified of a change to an open relation (one with
+ * refcount > 0).
+ *
+ * NB: when rebuilding, we'd better hold some lock on the relation,
+ * else the catalog data we need to read could be changing under us.
+ * Also, a rel to be rebuilt had better have refcnt > 0. This is because
+ * an sinval reset could happen while we're accessing the catalogs, and
+ * the rel would get blown away underneath us by RelationCacheInvalidate
+ * if it has zero refcnt.
+ *
+ * The "rebuild" parameter is redundant in current usage because it has
+ * to match the relation's refcnt status, but we keep it as a crosscheck
+ * that we're doing what the caller expects.
*/
static void
RelationClearRelation(Relation relation, bool rebuild)
{
- Oid old_reltype = relation->rd_rel->reltype;
- MemoryContext oldcxt;
+ /*
+ * As per notes above, a rel to be rebuilt MUST have refcnt > 0; while of
+ * course it would be a bad idea to blow away one with nonzero refcnt.
+ */
+ Assert(rebuild ?
+ !RelationHasReferenceCountZero(relation) :
+ RelationHasReferenceCountZero(relation));
/*
* Make sure smgr and lower levels close the relation's files, if they
- * weren't closed already. If the relation is not getting deleted,
- * the next smgr access should reopen the files automatically. This
- * ensures that the low-level file access state is updated after, say,
- * a vacuum truncation.
+ * weren't closed already. If the relation is not getting deleted, the
+ * next smgr access should reopen the files automatically. This ensures
+ * that the low-level file access state is updated after, say, a vacuum
+ * truncation.
*/
RelationCloseSmgr(relation);
/*
- * Never, never ever blow away a nailed-in system relation, because
- * we'd be unable to recover. However, we must reset rd_targblock, in
- * case we got called because of a relation cache flush that was
- * triggered by VACUUM.
+ * Never, never ever blow away a nailed-in system relation, because we'd
+ * be unable to recover. However, we must redo RelationInitPhysicalAddr
+ * in case it is a mapped relation whose mapping changed.
*
- * If it's a nailed index, then we need to re-read the pg_class row to
- * see if its relfilenode changed. We can't necessarily do that here,
- * because we might be in a failed transaction. We assume it's okay
- * to do it if there are open references to the relcache entry (cf
- * notes for AtEOXact_RelationCache). Otherwise just mark the entry
- * as possibly invalid, and it'll be fixed when next opened.
+ * If it's a nailed index, then we need to re-read the pg_class row to see
+ * if its relfilenode changed. We can't necessarily do that here, because
+ * we might be in a failed transaction. We assume it's okay to do it if
+ * there are open references to the relcache entry (cf notes for
+ * AtEOXact_RelationCache). Otherwise just mark the entry as possibly
+ * invalid, and it'll be fixed when next opened.
*/
if (relation->rd_isnailed)
{
- relation->rd_targblock = InvalidBlockNumber;
+ RelationInitPhysicalAddr(relation);
+
if (relation->rd_rel->relkind == RELKIND_INDEX)
{
relation->rd_isvalid = false; /* needs to be revalidated */
if (relation->rd_refcnt > 1)
- RelationReloadClassinfo(relation);
+ RelationReloadIndexInfo(relation);
}
return;
}
/*
- * Remove relation from hash tables
- *
- * Note: we might be reinserting it momentarily, but we must not have it
- * visible in the hash tables until it's valid again, so don't try to
- * optimize this away...
+ * Even non-system indexes should not be blown away if they are open and
+ * have valid index support information. This avoids problems with active
+ * use of the index support information. As with nailed indexes, we
+ * re-read the pg_class row to handle possible physical relocation of the
+ * index, and we check for pg_index updates too.
*/
- oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
- RelationCacheDelete(relation);
- MemoryContextSwitchTo(oldcxt);
-
- /* Clear out catcache's entries for this relation */
- CatalogCacheFlushRelation(RelationGetRelid(relation));
+ if (relation->rd_rel->relkind == RELKIND_INDEX &&
+ relation->rd_refcnt > 0 &&
+ relation->rd_indexcxt != NULL)
+ {
+ relation->rd_isvalid = false; /* needs to be revalidated */
+ RelationReloadIndexInfo(relation);
+ return;
+ }
- /*
- * Free all the subsidiary data structures of the relcache entry. We
- * cannot free rd_att if we are trying to rebuild the entry, however,
- * because pointers to it may be cached in various places. The rule
- * manager might also have pointers into the rewrite rules. So to
- * begin with, we can only get rid of these fields:
- */
- FreeTriggerDesc(relation->trigdesc);
- if (relation->rd_indextuple)
- pfree(relation->rd_indextuple);
- if (relation->rd_am)
- pfree(relation->rd_am);
- if (relation->rd_rel)
- pfree(relation->rd_rel);
- list_free(relation->rd_indexlist);
- if (relation->rd_indexcxt)
- MemoryContextDelete(relation->rd_indexcxt);
+ /* Mark it invalid until we've finished rebuild */
+ relation->rd_isvalid = false;
/*
* If we're really done with the relcache entry, blow it away. But if
- * someone is still using it, reconstruct the whole deal without
- * moving the physical RelationData record (so that the someone's
- * pointer is still valid).
+ * someone is still using it, reconstruct the whole deal without moving
+ * the physical RelationData record (so that the someone's pointer is
+ * still valid).
*/
if (!rebuild)
{
- /* ok to zap remaining substructure */
- flush_rowtype_cache(old_reltype);
- FreeTupleDesc(relation->rd_att);
- if (relation->rd_rulescxt)
- MemoryContextDelete(relation->rd_rulescxt);
- pfree(relation);
+ /* Remove it from the hash table */
+ RelationCacheDelete(relation);
+
+ /* And release storage */
+ RelationDestroyRelation(relation);
}
else
{
/*
- * When rebuilding an open relcache entry, must preserve ref count
- * and rd_createSubid state. Also attempt to preserve the
- * tupledesc and rewrite-rule substructures in place.
+ * Our strategy for rebuilding an open relcache entry is to build a
+ * new entry from scratch, swap its contents with the old entry, and
+ * finally delete the new entry (along with any infrastructure swapped
+ * over from the old entry). This is to avoid trouble in case an
+ * error causes us to lose control partway through. The old entry
+ * will still be marked !rd_isvalid, so we'll try to rebuild it again
+ * on next access. Meanwhile it's not any less valid than it was
+ * before, so any code that might expect to continue accessing it
+ * isn't hurt by the rebuild failure. (Consider for example a
+ * subtransaction that ALTERs a table and then gets cancelled partway
+ * through the cache entry rebuild. The outer transaction should
+ * still see the not-modified cache entry as valid.) The worst
+ * consequence of an error is leaking the necessarily-unreferenced new
+ * entry, and this shouldn't happen often enough for that to be a big
+ * problem.
+ *
+ * When rebuilding an open relcache entry, we must preserve ref count,
+ * rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state. Also
+ * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
+ * rewrite-rule substructures in place, because various places assume
+ * that these structures won't move while they are working with an
+ * open relcache entry. (Note: the refcount mechanism for tupledescs
+ * might someday allow us to remove this hack for the tupledesc.)
*
* Note that this process does not touch CurrentResourceOwner; which
* is good because whatever ref counts the entry may have do not
* necessarily belong to that resource owner.
*/
+ Relation newrel;
Oid save_relid = RelationGetRelid(relation);
- int old_refcnt = relation->rd_refcnt;
- SubTransactionId old_createSubid = relation->rd_createSubid;
- TupleDesc old_att = relation->rd_att;
- RuleLock *old_rules = relation->rd_rules;
- MemoryContext old_rulescxt = relation->rd_rulescxt;
+ bool keep_tupdesc;
+ bool keep_rules;
- if (RelationBuildDesc(save_relid, relation) != relation)
+ /* Build temporary entry, but don't link it into hashtable */
+ newrel = RelationBuildDesc(save_relid, false);
+ if (newrel == NULL)
{
/* Should only get here if relation was deleted */
- flush_rowtype_cache(old_reltype);
- FreeTupleDesc(old_att);
- if (old_rulescxt)
- MemoryContextDelete(old_rulescxt);
- pfree(relation);
+ RelationCacheDelete(relation);
+ RelationDestroyRelation(relation);
elog(ERROR, "relation %u deleted while still in use", save_relid);
}
- relation->rd_refcnt = old_refcnt;
- relation->rd_createSubid = old_createSubid;
- if (equalTupleDescs(old_att, relation->rd_att))
- {
- /* needn't flush typcache here */
- FreeTupleDesc(relation->rd_att);
- relation->rd_att = old_att;
- }
- else
- {
- flush_rowtype_cache(old_reltype);
- FreeTupleDesc(old_att);
- }
- if (equalRuleLocks(old_rules, relation->rd_rules))
+
+ keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
+ keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
+
+ /*
+ * Perform swapping of the relcache entry contents. Within this
+ * process the old entry is momentarily invalid, so there *must* be no
+ * possibility of CHECK_FOR_INTERRUPTS within this sequence. Do it in
+ * all-in-line code for safety.
+ *
+ * Since the vast majority of fields should be swapped, our method is
+ * to swap the whole structures and then re-swap those few fields we
+ * didn't want swapped.
+ */
+#define SWAPFIELD(fldtype, fldname) \
+ do { \
+ fldtype _tmp = newrel->fldname; \
+ newrel->fldname = relation->fldname; \
+ relation->fldname = _tmp; \
+ } while (0)
+
+ /* swap all Relation struct fields */
{
- if (relation->rd_rulescxt)
- MemoryContextDelete(relation->rd_rulescxt);
- relation->rd_rules = old_rules;
- relation->rd_rulescxt = old_rulescxt;
+ RelationData tmpstruct;
+
+ memcpy(&tmpstruct, newrel, sizeof(RelationData));
+ memcpy(newrel, relation, sizeof(RelationData));
+ memcpy(relation, &tmpstruct, sizeof(RelationData));
}
- else
+
+ /* rd_smgr must not be swapped, due to back-links from smgr level */
+ SWAPFIELD(SMgrRelation, rd_smgr);
+ /* rd_refcnt must be preserved */
+ SWAPFIELD(int, rd_refcnt);
+ /* isnailed shouldn't change */
+ Assert(newrel->rd_isnailed == relation->rd_isnailed);
+ /* creation sub-XIDs must be preserved */
+ SWAPFIELD(SubTransactionId, rd_createSubid);
+ SWAPFIELD(SubTransactionId, rd_newRelfilenodeSubid);
+ /* un-swap rd_rel pointers, swap contents instead */
+ SWAPFIELD(Form_pg_class, rd_rel);
+ /* ... but actually, we don't have to update newrel->rd_rel */
+ memcpy(relation->rd_rel, newrel->rd_rel, CLASS_TUPLE_SIZE);
+ /* preserve old tupledesc and rules if no logical change */
+ if (keep_tupdesc)
+ SWAPFIELD(TupleDesc, rd_att);
+ if (keep_rules)
{
- if (old_rulescxt)
- MemoryContextDelete(old_rulescxt);
+ SWAPFIELD(RuleLock *, rd_rules);
+ SWAPFIELD(MemoryContext, rd_rulescxt);
}
+ /* toast OID override must be preserved */
+ SWAPFIELD(Oid, rd_toastoid);
+ /* pgstat_info must be preserved */
+ SWAPFIELD(struct PgStat_TableStatus *, pgstat_info);
+
+#undef SWAPFIELD
+
+ /* And now we can throw away the temporary entry */
+ RelationDestroyRelation(newrel);
}
}
static void
RelationFlushRelation(Relation relation)
{
- bool rebuild;
-
- if (relation->rd_createSubid != InvalidSubTransactionId)
+ if (relation->rd_createSubid != InvalidSubTransactionId ||
+ relation->rd_newRelfilenodeSubid != InvalidSubTransactionId)
{
/*
* New relcache entries are always rebuilt, not flushed; else we'd
* forget the "new" status of the relation, which is a useful
- * optimization to have.
+ * optimization to have. Ditto for the new-relfilenode status.
+ *
+ * The rel could have zero refcnt here, so temporarily increment the
+ * refcnt to ensure it's safe to rebuild it. We can assume that the
+ * current transaction has some lock on the rel already.
*/
- rebuild = true;
+ RelationIncrementReferenceCount(relation);
+ RelationClearRelation(relation, true);
+ RelationDecrementReferenceCount(relation);
}
else
{
/*
* Pre-existing rels can be dropped from the relcache if not open.
*/
- rebuild = !RelationHasReferenceCountZero(relation);
- }
+ bool rebuild = !RelationHasReferenceCountZero(relation);
- RelationClearRelation(relation, rebuild);
+ RelationClearRelation(relation, rebuild);
+ }
}
/*
* RelationCacheInvalidate
* Blow away cached relation descriptors that have zero reference counts,
* and rebuild those with positive reference counts. Also reset the smgr
- * relation cache.
+ * relation cache and re-read relation mapping data.
*
* This is currently used only to recover from SI message buffer overflow,
* so we do not touch new-in-transaction relations; they cannot be targets
* of cross-backend SI updates (and our own updates now go through a
* separate linked list that isn't limited by the SI message buffer size).
+ * Likewise, we need not discard new-relfilenode-in-transaction hints,
+ * since any invalidation of those would be a local event.
*
* We do this in two phases: the first pass deletes deletable items, and
* the second one rebuilds the rebuildable items. This is essential for
{
/*
* Add this entry to list of stuff to rebuild in second pass.
- * pg_class_oid_index goes on the front of rebuildFirstList,
- * other nailed indexes on the back, and everything else into
+ * pg_class_oid_index goes on the front of rebuildFirstList, other
+ * nailed indexes on the back, and everything else into
* rebuildList (in no particular order).
*/
if (relation->rd_isnailed &&
}
}
- rebuildList = list_concat(rebuildFirstList, rebuildList);
-
/*
- * Now zap any remaining smgr cache entries. This must happen before
- * we start to rebuild entries, since that may involve catalog fetches
- * which will re-open catalog files.
+ * Now zap any remaining smgr cache entries. This must happen before we
+ * start to rebuild entries, since that may involve catalog fetches which
+ * will re-open catalog files.
*/
smgrcloseall();
+ /*
+ * Reload relation mapping data before starting to reconstruct cache.
+ */
+ RelationMapInvalidateAll();
+
/* Phase 2: rebuild the items found to need rebuild in phase 1 */
+ foreach(l, rebuildFirstList)
+ {
+ relation = (Relation) lfirst(l);
+ RelationClearRelation(relation, true);
+ }
+ list_free(rebuildFirstList);
foreach(l, rebuildList)
{
relation = (Relation) lfirst(l);
}
/*
+ * RelationCloseSmgrByOid - close a relcache entry's smgr link
+ *
+ * Needed in some cases where we are changing a relation's physical mapping.
+ * The link will be automatically reopened on next use.
+ */
+void
+RelationCloseSmgrByOid(Oid relationId)
+{
+ Relation relation;
+
+ RelationIdCacheLookup(relationId, relation);
+
+ if (!PointerIsValid(relation))
+ return; /* not in cache, nothing to do */
+
+ RelationCloseSmgr(relation);
+}
+
+/*
* AtEOXact_RelationCache
*
* Clean up the relcache at main-transaction commit or abort.
/*
* To speed up transaction exit, we want to avoid scanning the relcache
- * unless there is actually something for this routine to do. Other
- * than the debug-only Assert checks, most transactions don't create
- * any work for us to do here, so we keep a static flag that gets set
- * if there is anything to do. (Currently, this means either a relation
- * is created in the current xact, or an index list is forced.) For
- * simplicity, the flag remains set till end of top-level transaction,
- * even though we could clear it at subtransaction end in some cases.
+ * unless there is actually something for this routine to do. Other than
+ * the debug-only Assert checks, most transactions don't create any work
+ * for us to do here, so we keep a static flag that gets set if there is
+ * anything to do. (Currently, this means either a relation is created in
+ * the current xact, or one is given a new relfilenode, or an index list
+ * is forced.) For simplicity, the flag remains set till end of top-level
+ * transaction, even though we could clear it at subtransaction end in
+ * some cases.
*/
if (!need_eoxact_work
#ifdef USE_ASSERT_CHECKING
* The relcache entry's ref count should be back to its normal
* not-in-a-transaction state: 0 unless it's nailed in cache.
*
- * In bootstrap mode, this is NOT true, so don't check it ---
- * the bootstrap code expects relations to stay open across
- * start/commit transaction calls. (That seems bogus, but it's
- * not worth fixing.)
+ * In bootstrap mode, this is NOT true, so don't check it --- the
+ * bootstrap code expects relations to stay open across start/commit
+ * transaction calls. (That seems bogus, but it's not worth fixing.)
*/
#ifdef USE_ASSERT_CHECKING
if (!IsBootstrapProcessingMode())
}
/*
+ * Likewise, reset the hint about the relfilenode being new.
+ */
+ relation->rd_newRelfilenodeSubid = InvalidSubTransactionId;
+
+ /*
* Flush any temporary index list.
*/
if (relation->rd_indexvalid == 2)
relation->rd_createSubid = parentSubid;
else
{
- Assert(RelationHasReferenceCountZero(relation));
RelationClearRelation(relation, false);
continue;
}
}
/*
+ * Likewise, update or drop any new-relfilenode-in-subtransaction
+ * hint.
+ */
+ if (relation->rd_newRelfilenodeSubid == mySubid)
+ {
+ if (isCommit)
+ relation->rd_newRelfilenodeSubid = parentSubid;
+ else
+ relation->rd_newRelfilenodeSubid = InvalidSubTransactionId;
+ }
+
+ /*
* Flush any temporary index list.
*/
if (relation->rd_indexvalid == 2)
}
}
+
/*
* RelationBuildLocalRelation
* Build a relcache entry for an about-to-be-created relation,
TupleDesc tupDesc,
Oid relid,
Oid reltablespace,
- bool shared_relation)
+ bool shared_relation,
+ bool mapped_relation,
+ char relpersistence)
{
Relation rel;
MemoryContext oldcxt;
/*
* check for creation of a rel that must be nailed in cache.
*
- * XXX this list had better match RelationCacheInitialize's list.
+ * XXX this list had better match the relations specially handled in
+ * RelationCacheInitializePhase2/3.
*/
switch (relid)
{
+ case DatabaseRelationId:
+ case AuthIdRelationId:
+ case AuthMemRelationId:
case RelationRelationId:
case AttributeRelationId:
case ProcedureRelationId:
}
/*
+ * check that hardwired list of shared rels matches what's in the
+ * bootstrap .bki file. If you get a failure here during initdb, you
+ * probably need to fix IsSharedRelation() to match whatever you've done
+ * to the set of shared relations.
+ */
+ if (shared_relation != IsSharedRelation(relid))
+ elog(ERROR, "shared_relation flag for \"%s\" does not match IsSharedRelation(%u)",
+ relname, relid);
+
+ /* Shared relations had better be mapped, too */
+ Assert(mapped_relation || !shared_relation);
+
+ /*
* switch to the cache context to create the relcache entry.
*/
if (!CacheMemoryContext)
*/
rel = (Relation) palloc0(sizeof(RelationData));
- rel->rd_targblock = InvalidBlockNumber;
-
/* make sure relation is marked as having no open file yet */
rel->rd_smgr = NULL;
/* it's being created in this transaction */
rel->rd_createSubid = GetCurrentSubTransactionId();
+ rel->rd_newRelfilenodeSubid = InvalidSubTransactionId;
/* must flag that we have rels created in this transaction */
need_eoxact_work = true;
- /* is it a temporary relation? */
- rel->rd_istemp = isTempNamespace(relnamespace);
-
/*
* create a new tuple descriptor from the one passed in. We do this
- * partly to copy it into the cache context, and partly because the
- * new relation can't have any defaults or constraints yet; they have
- * to be added in later steps, because they require additions to
- * multiple system catalogs. We can copy attnotnull constraints here,
- * however.
+ * partly to copy it into the cache context, and partly because the new
+ * relation can't have any defaults or constraints yet; they have to be
+ * added in later steps, because they require additions to multiple system
+ * catalogs. We can copy attnotnull constraints here, however.
*/
rel->rd_att = CreateTupleDescCopy(tupDesc);
+ rel->rd_att->tdrefcount = 1; /* mark as refcounted */
has_not_null = false;
for (i = 0; i < natts; i++)
{
/* needed when bootstrapping: */
rel->rd_rel->relowner = BOOTSTRAP_SUPERUSERID;
+ /* set up persistence; rd_backend is a function of persistence type */
+ rel->rd_rel->relpersistence = relpersistence;
+ switch (relpersistence)
+ {
+ case RELPERSISTENCE_PERMANENT:
+ rel->rd_backend = InvalidBackendId;
+ break;
+ case RELPERSISTENCE_TEMP:
+ rel->rd_backend = MyBackendId;
+ break;
+ default:
+ elog(ERROR, "invalid relpersistence: %c", relpersistence);
+ break;
+ }
+
/*
- * Insert relation physical and logical identifiers (OIDs) into the
- * right places. Note that the physical ID (relfilenode) is initially
- * the same as the logical ID (OID).
+ * Insert relation physical and logical identifiers (OIDs) into the right
+ * places. Note that the physical ID (relfilenode) is initially the same
+ * as the logical ID (OID); except that for a mapped relation, we set
+ * relfilenode to zero and rely on RelationInitPhysicalAddr to consult the
+ * map.
*/
rel->rd_rel->relisshared = shared_relation;
for (i = 0; i < natts; i++)
rel->rd_att->attrs[i]->attrelid = relid;
- rel->rd_rel->relfilenode = relid;
rel->rd_rel->reltablespace = reltablespace;
+ if (mapped_relation)
+ {
+ rel->rd_rel->relfilenode = InvalidOid;
+ /* Add it to the active mapping information */
+ RelationMapUpdateMap(relid, relid, shared_relation, true);
+ }
+ else
+ rel->rd_rel->relfilenode = relid;
+
RelationInitLockInfo(rel); /* see lmgr.c */
RelationInitPhysicalAddr(rel);
return rel;
}
+
+/*
+ * RelationSetNewRelfilenode
+ *
+ * Assign a new relfilenode (physical file name) to the relation.
+ *
+ * This allows a full rewrite of the relation to be done with transactional
+ * safety (since the filenode assignment can be rolled back). Note however
+ * that there is no simple way to access the relation's old data for the
+ * remainder of the current transaction. This limits the usefulness to cases
+ * such as TRUNCATE or rebuilding an index from scratch.
+ *
+ * Caller must already hold exclusive lock on the relation.
+ *
+ * The relation is marked with relfrozenxid = freezeXid (InvalidTransactionId
+ * must be passed for indexes and sequences). This should be a lower bound on
+ * the XIDs that will be put into the new relation contents.
+ */
+void
+RelationSetNewRelfilenode(Relation relation, TransactionId freezeXid)
+{
+ Oid newrelfilenode;
+ RelFileNodeBackend newrnode;
+ Relation pg_class;
+ HeapTuple tuple;
+ Form_pg_class classform;
+
+ /* Indexes, sequences must have Invalid frozenxid; other rels must not */
+ Assert((relation->rd_rel->relkind == RELKIND_INDEX ||
+ relation->rd_rel->relkind == RELKIND_SEQUENCE) ?
+ freezeXid == InvalidTransactionId :
+ TransactionIdIsNormal(freezeXid));
+
+ /* Allocate a new relfilenode */
+ newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
+ relation->rd_rel->relpersistence);
+
+ /*
+ * Get a writable copy of the pg_class tuple for the given relation.
+ */
+ pg_class = heap_open(RelationRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "could not find tuple for relation %u",
+ RelationGetRelid(relation));
+ classform = (Form_pg_class) GETSTRUCT(tuple);
+
+ /*
+ * Create storage for the main fork of the new relfilenode.
+ *
+ * NOTE: any conflict in relfilenode value will be caught here, if
+ * GetNewRelFileNode messes up for any reason.
+ */
+ newrnode.node = relation->rd_node;
+ newrnode.node.relNode = newrelfilenode;
+ newrnode.backend = relation->rd_backend;
+ RelationCreateStorage(newrnode.node, relation->rd_rel->relpersistence);
+ smgrclosenode(newrnode);
+
+ /*
+ * Schedule unlinking of the old storage at transaction commit.
+ */
+ RelationDropStorage(relation);
+
+ /*
+ * Now update the pg_class row. However, if we're dealing with a mapped
+ * index, pg_class.relfilenode doesn't change; instead we have to send the
+ * update to the relation mapper.
+ */
+ if (RelationIsMapped(relation))
+ RelationMapUpdateMap(RelationGetRelid(relation),
+ newrelfilenode,
+ relation->rd_rel->relisshared,
+ false);
+ else
+ classform->relfilenode = newrelfilenode;
+
+ /* These changes are safe even for a mapped relation */
+ if (relation->rd_rel->relkind != RELKIND_SEQUENCE)
+ {
+ classform->relpages = 0; /* it's empty until further notice */
+ classform->reltuples = 0;
+ }
+ classform->relfrozenxid = freezeXid;
+
+ simple_heap_update(pg_class, &tuple->t_self, tuple);
+ CatalogUpdateIndexes(pg_class, tuple);
+
+ heap_freetuple(tuple);
+
+ heap_close(pg_class, RowExclusiveLock);
+
+ /*
+ * Make the pg_class row change visible, as well as the relation map
+ * change if any. This will cause the relcache entry to get updated, too.
+ */
+ CommandCounterIncrement();
+
+ /*
+ * Mark the rel as having been given a new relfilenode in the current
+ * (sub) transaction. This is a hint that can be used to optimize later
+ * operations on the rel in the same transaction.
+ */
+ relation->rd_newRelfilenodeSubid = GetCurrentSubTransactionId();
+ /* ... and now we have eoxact cleanup work to do */
+ need_eoxact_work = true;
+}
+
+
/*
* RelationCacheInitialize
*
* This initializes the relation descriptor cache. At the time
* that this is invoked, we can't do database access yet (mainly
- * because the transaction subsystem is not up), so we can't get
- * "real" info. However it's okay to read the pg_internal.init
- * cache file, if one is available. Otherwise we make phony
- * entries for the minimum set of nailed-in-cache relations.
+ * because the transaction subsystem is not up); all we are doing
+ * is making an empty cache hashtable. This must be done before
+ * starting the initialization transaction, because otherwise
+ * AtEOXact_RelationCache would crash if that transaction aborts
+ * before we can get the relcache set up.
*/
#define INITRELCACHESIZE 400
void
RelationCacheInitialize(void)
{
- MemoryContext oldcxt;
HASHCTL ctl;
/*
- * switch to cache memory context
+ * make sure cache memory context exists
*/
if (!CacheMemoryContext)
CreateCacheMemoryContext();
- oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
-
/*
- * create hashtables that index the relcache
+ * create hashtable that indexes the relcache
*/
MemSet(&ctl, 0, sizeof(ctl));
ctl.keysize = sizeof(Oid);
&ctl, HASH_ELEM | HASH_FUNCTION);
/*
- * Try to load the relcache cache file. If successful, we're done for
- * now. Otherwise, initialize the cache with pre-made descriptors for
- * the critical "nailed-in" system catalogs.
+ * relation mapper needs to be initialized too
*/
- if (IsBootstrapProcessingMode() ||
- !load_relcache_init_file())
- {
- formrdesc("pg_class", PG_CLASS_RELTYPE_OID,
- true, Natts_pg_class, Desc_pg_class);
- formrdesc("pg_attribute", PG_ATTRIBUTE_RELTYPE_OID,
- false, Natts_pg_attribute, Desc_pg_attribute);
- formrdesc("pg_proc", PG_PROC_RELTYPE_OID,
- true, Natts_pg_proc, Desc_pg_proc);
- formrdesc("pg_type", PG_TYPE_RELTYPE_OID,
- true, Natts_pg_type, Desc_pg_type);
+ RelationMapInitialize();
+}
+
+/*
+ * RelationCacheInitializePhase2
+ *
+ * This is called to prepare for access to shared catalogs during startup.
+ * We must at least set up nailed reldescs for pg_database, pg_authid,
+ * and pg_auth_members. Ideally we'd like to have reldescs for their
+ * indexes, too. We attempt to load this information from the shared
+ * relcache init file. If that's missing or broken, just make phony
+ * entries for the catalogs themselves. RelationCacheInitializePhase3
+ * will clean up as needed.
+ */
+void
+RelationCacheInitializePhase2(void)
+{
+ MemoryContext oldcxt;
+
+ /*
+ * relation mapper needs initialized too
+ */
+ RelationMapInitializePhase2();
+
+ /*
+ * In bootstrap mode, the shared catalogs aren't there yet anyway, so do
+ * nothing.
+ */
+ if (IsBootstrapProcessingMode())
+ return;
-#define NUM_CRITICAL_RELS 4 /* fix if you change list above */
+ /*
+ * switch to cache memory context
+ */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+ /*
+ * Try to load the shared relcache cache file. If unsuccessful, bootstrap
+ * the cache with pre-made descriptors for the critical shared catalogs.
+ */
+ if (!load_relcache_init_file(true))
+ {
+ formrdesc("pg_database", DatabaseRelation_Rowtype_Id, true,
+ true, Natts_pg_database, Desc_pg_database);
+ formrdesc("pg_authid", AuthIdRelation_Rowtype_Id, true,
+ true, Natts_pg_authid, Desc_pg_authid);
+ formrdesc("pg_auth_members", AuthMemRelation_Rowtype_Id, true,
+ false, Natts_pg_auth_members, Desc_pg_auth_members);
+
+#define NUM_CRITICAL_SHARED_RELS 3 /* fix if you change list above */
}
MemoryContextSwitchTo(oldcxt);
}
/*
- * RelationCacheInitializePhase2
+ * RelationCacheInitializePhase3
*
* This is called as soon as the catcache and transaction system
- * are functional. At this point we can actually read data from
- * the system catalogs. Update the relcache entries made during
- * RelationCacheInitialize, and make sure we have entries for the
- * critical system indexes.
+ * are functional and we have determined MyDatabaseId. At this point
+ * we can actually read data from the database's system catalogs.
+ * We first try to read pre-computed relcache entries from the local
+ * relcache init file. If that's missing or broken, make phony entries
+ * for the minimum set of nailed-in-cache relations. Then (unless
+ * bootstrapping) make sure we have entries for the critical system
+ * indexes. Once we've done all this, we have enough infrastructure to
+ * open any system catalog or use any catcache. The last step is to
+ * rewrite the cache files if needed.
*/
void
-RelationCacheInitializePhase2(void)
+RelationCacheInitializePhase3(void)
{
HASH_SEQ_STATUS status;
RelIdCacheEnt *idhentry;
+ MemoryContext oldcxt;
+ bool needNewCacheFile = !criticalSharedRelcachesBuilt;
+
+ /*
+ * relation mapper needs initialized too
+ */
+ RelationMapInitializePhase3();
+
+ /*
+ * switch to cache memory context
+ */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+ /*
+ * Try to load the local relcache cache file. If unsuccessful, bootstrap
+ * the cache with pre-made descriptors for the critical "nailed-in" system
+ * catalogs.
+ */
+ if (IsBootstrapProcessingMode() ||
+ !load_relcache_init_file(false))
+ {
+ needNewCacheFile = true;
+
+ formrdesc("pg_class", RelationRelation_Rowtype_Id, false,
+ true, Natts_pg_class, Desc_pg_class);
+ formrdesc("pg_attribute", AttributeRelation_Rowtype_Id, false,
+ false, Natts_pg_attribute, Desc_pg_attribute);
+ formrdesc("pg_proc", ProcedureRelation_Rowtype_Id, false,
+ true, Natts_pg_proc, Desc_pg_proc);
+ formrdesc("pg_type", TypeRelation_Rowtype_Id, false,
+ true, Natts_pg_type, Desc_pg_type);
+
+#define NUM_CRITICAL_LOCAL_RELS 4 /* fix if you change list above */
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+ /* In bootstrap mode, the faked-up formrdesc info is all we'll have */
if (IsBootstrapProcessingMode())
return;
/*
- * If we didn't get the critical system indexes loaded into relcache,
- * do so now. These are critical because the catcache depends on them
- * for catcache fetches that are done during relcache load. Thus, we
- * have an infinite-recursion problem. We can break the recursion by
- * doing heapscans instead of indexscans at certain key spots. To
- * avoid hobbling performance, we only want to do that until we have
- * the critical indexes loaded into relcache. Thus, the flag
- * criticalRelcachesBuilt is used to decide whether to do heapscan or
- * indexscan at the key spots, and we set it true after we've loaded
- * the critical indexes.
+ * If we didn't get the critical system indexes loaded into relcache, do
+ * so now. These are critical because the catcache and/or opclass cache
+ * depend on them for fetches done during relcache load. Thus, we have an
+ * infinite-recursion problem. We can break the recursion by doing
+ * heapscans instead of indexscans at certain key spots. To avoid hobbling
+ * performance, we only want to do that until we have the critical indexes
+ * loaded into relcache. Thus, the flag criticalRelcachesBuilt is used to
+ * decide whether to do heapscan or indexscan at the key spots, and we set
+ * it true after we've loaded the critical indexes.
+ *
+ * The critical indexes are marked as "nailed in cache", partly to make it
+ * easy for load_relcache_init_file to count them, but mainly because we
+ * cannot flush and rebuild them once we've set criticalRelcachesBuilt to
+ * true. (NOTE: perhaps it would be possible to reload them by
+ * temporarily setting criticalRelcachesBuilt to false again. For now,
+ * though, we just nail 'em in.)
*
- * The critical indexes are marked as "nailed in cache", partly to make
- * it easy for load_relcache_init_file to count them, but mainly
- * because we cannot flush and rebuild them once we've set
- * criticalRelcachesBuilt to true. (NOTE: perhaps it would be
- * possible to reload them by temporarily setting
- * criticalRelcachesBuilt to false again. For now, though, we just
- * nail 'em in.)
+ * RewriteRelRulenameIndexId and TriggerRelidNameIndexId are not critical
+ * in the same way as the others, because the critical catalogs don't
+ * (currently) have any rules or triggers, and so these indexes can be
+ * rebuilt without inducing recursion. However they are used during
+ * relcache load when a rel does have rules or triggers, so we choose to
+ * nail them for performance reasons.
*/
if (!criticalRelcachesBuilt)
{
- Relation ird;
-
-#define LOAD_CRIT_INDEX(indexoid) \
- do { \
- ird = RelationBuildDesc((indexoid), NULL); \
- ird->rd_isnailed = true; \
- ird->rd_refcnt = 1; \
- } while (0)
-
- LOAD_CRIT_INDEX(ClassOidIndexId);
- LOAD_CRIT_INDEX(AttributeRelidNumIndexId);
- LOAD_CRIT_INDEX(IndexRelidIndexId);
- LOAD_CRIT_INDEX(AccessMethodStrategyIndexId);
- LOAD_CRIT_INDEX(AccessMethodProcedureIndexId);
- LOAD_CRIT_INDEX(OperatorOidIndexId);
-
-#define NUM_CRITICAL_INDEXES 6 /* fix if you change list above */
+ load_critical_index(ClassOidIndexId,
+ RelationRelationId);
+ load_critical_index(AttributeRelidNumIndexId,
+ AttributeRelationId);
+ load_critical_index(IndexRelidIndexId,
+ IndexRelationId);
+ load_critical_index(OpclassOidIndexId,
+ OperatorClassRelationId);
+ load_critical_index(AccessMethodProcedureIndexId,
+ AccessMethodProcedureRelationId);
+ load_critical_index(RewriteRelRulenameIndexId,
+ RewriteRelationId);
+ load_critical_index(TriggerRelidNameIndexId,
+ TriggerRelationId);
+
+#define NUM_CRITICAL_LOCAL_INDEXES 7 /* fix if you change list above */
criticalRelcachesBuilt = true;
}
/*
- * Now, scan all the relcache entries and update anything that might
- * be wrong in the results from formrdesc or the relcache cache file.
- * If we faked up relcache entries using formrdesc, then read the real
- * pg_class rows and replace the fake entries with them. Also, if any
- * of the relcache entries have rules or triggers, load that info the
- * hard way since it isn't recorded in the cache file.
+ * Process critical shared indexes too.
+ *
+ * DatabaseNameIndexId isn't critical for relcache loading, but rather for
+ * initial lookup of MyDatabaseId, without which we'll never find any
+ * non-shared catalogs at all. Autovacuum calls InitPostgres with a
+ * database OID, so it instead depends on DatabaseOidIndexId. We also
+ * need to nail up some indexes on pg_authid and pg_auth_members for use
+ * during client authentication.
+ */
+ if (!criticalSharedRelcachesBuilt)
+ {
+ load_critical_index(DatabaseNameIndexId,
+ DatabaseRelationId);
+ load_critical_index(DatabaseOidIndexId,
+ DatabaseRelationId);
+ load_critical_index(AuthIdRolnameIndexId,
+ AuthIdRelationId);
+ load_critical_index(AuthIdOidIndexId,
+ AuthIdRelationId);
+ load_critical_index(AuthMemMemRoleIndexId,
+ AuthMemRelationId);
+
+#define NUM_CRITICAL_SHARED_INDEXES 5 /* fix if you change list above */
+
+ criticalSharedRelcachesBuilt = true;
+ }
+
+ /*
+ * Now, scan all the relcache entries and update anything that might be
+ * wrong in the results from formrdesc or the relcache cache file. If we
+ * faked up relcache entries using formrdesc, then read the real pg_class
+ * rows and replace the fake entries with them. Also, if any of the
+ * relcache entries have rules or triggers, load that info the hard way
+ * since it isn't recorded in the cache file.
+ *
+ * Whenever we access the catalogs to read data, there is a possibility of
+ * a shared-inval cache flush causing relcache entries to be removed.
+ * Since hash_seq_search only guarantees to still work after the *current*
+ * entry is removed, it's unsafe to continue the hashtable scan afterward.
+ * We handle this by restarting the scan from scratch after each access.
+ * This is theoretically O(N^2), but the number of entries that actually
+ * need to be fixed is small enough that it doesn't matter.
*/
hash_seq_init(&status, RelationIdCache);
while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
{
Relation relation = idhentry->reldesc;
+ bool restart = false;
+
+ /*
+ * Make sure *this* entry doesn't get flushed while we work with it.
+ */
+ RelationIncrementReferenceCount(relation);
/*
* If it's a faked-up entry, read the real pg_class tuple.
*/
- if (needNewCacheFile && relation->rd_isnailed)
+ if (relation->rd_rel->relowner == InvalidOid)
{
HeapTuple htup;
Form_pg_class relp;
- htup = SearchSysCache(RELOID,
- ObjectIdGetDatum(RelationGetRelid(relation)),
- 0, 0, 0);
+ htup = SearchSysCache1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
if (!HeapTupleIsValid(htup))
elog(FATAL, "cache lookup failed for relation %u",
RelationGetRelid(relation));
* Copy tuple to relation->rd_rel. (See notes in
* AllocateRelationDesc())
*/
- Assert(relation->rd_rel != NULL);
memcpy((char *) relation->rd_rel, (char *) relp, CLASS_TUPLE_SIZE);
+ /* Update rd_options while we have the tuple */
+ if (relation->rd_options)
+ pfree(relation->rd_options);
+ RelationParseRelOptions(relation, htup);
+
/*
- * Also update the derived fields in rd_att.
+ * Check the values in rd_att were set up correctly. (We cannot
+ * just copy them over now: formrdesc must have set up the rd_att
+ * data correctly to start with, because it may already have been
+ * copied into one or more catcache entries.)
*/
- relation->rd_att->tdtypeid = relp->reltype;
- relation->rd_att->tdtypmod = -1; /* unnecessary, but... */
- relation->rd_att->tdhasoid = relp->relhasoids;
+ Assert(relation->rd_att->tdtypeid == relp->reltype);
+ Assert(relation->rd_att->tdtypmod == -1);
+ Assert(relation->rd_att->tdhasoid == relp->relhasoids);
ReleaseSysCache(htup);
+
+ /* relowner had better be OK now, else we'll loop forever */
+ if (relation->rd_rel->relowner == InvalidOid)
+ elog(ERROR, "invalid relowner in pg_class entry for \"%s\"",
+ RelationGetRelationName(relation));
+
+ restart = true;
}
/*
* Fix data that isn't saved in relcache cache file.
+ *
+ * relhasrules or relhastriggers could possibly be wrong or out of
+ * date. If we don't actually find any rules or triggers, clear the
+ * local copy of the flag so that we don't get into an infinite loop
+ * here. We don't make any attempt to fix the pg_class entry, though.
*/
if (relation->rd_rel->relhasrules && relation->rd_rules == NULL)
+ {
RelationBuildRuleLock(relation);
- if (relation->rd_rel->reltriggers > 0 && relation->trigdesc == NULL)
+ if (relation->rd_rules == NULL)
+ relation->rd_rel->relhasrules = false;
+ restart = true;
+ }
+ if (relation->rd_rel->relhastriggers && relation->trigdesc == NULL)
+ {
RelationBuildTriggers(relation);
- }
-}
+ if (relation->trigdesc == NULL)
+ relation->rd_rel->relhastriggers = false;
+ restart = true;
+ }
-/*
- * RelationCacheInitializePhase3
- *
- * Final step of relcache initialization: write out a new relcache
- * cache file if one is needed.
- */
-void
-RelationCacheInitializePhase3(void)
-{
- if (IsBootstrapProcessingMode())
- return;
+ /* Release hold on the relation */
+ RelationDecrementReferenceCount(relation);
+ /* Now, restart the hashtable scan if needed */
+ if (restart)
+ {
+ hash_seq_term(&status);
+ hash_seq_init(&status, RelationIdCache);
+ }
+ }
+
+ /*
+ * Lastly, write out new relcache cache files if needed. We don't bother
+ * to distinguish cases where only one of the two needs an update.
+ */
if (needNewCacheFile)
{
/*
- * Force all the catcaches to finish initializing and thereby open
- * the catalogs and indexes they use. This will preload the
- * relcache with entries for all the most important system
- * catalogs and indexes, so that the init file will be most useful
- * for future backends.
+ * Force all the catcaches to finish initializing and thereby open the
+ * catalogs and indexes they use. This will preload the relcache with
+ * entries for all the most important system catalogs and indexes, so
+ * that the init files will be most useful for future backends.
*/
InitCatalogCachePhase2();
- /* now write the file */
- write_relcache_init_file();
+ /* reset initFileRelationIds list; we'll fill it during write */
+ initFileRelationIds = NIL;
+
+ /* now write the files */
+ write_relcache_init_file(true);
+ write_relcache_init_file(false);
}
}
/*
+ * Load one critical system index into the relcache
+ *
+ * indexoid is the OID of the target index, heapoid is the OID of the catalog
+ * it belongs to.
+ */
+static void
+load_critical_index(Oid indexoid, Oid heapoid)
+{
+ Relation ird;
+
+ /*
+ * We must lock the underlying catalog before locking the index to avoid
+ * deadlock, since RelationBuildDesc might well need to read the catalog,
+ * and if anyone else is exclusive-locking this catalog and index they'll
+ * be doing it in that order.
+ */
+ LockRelationOid(heapoid, AccessShareLock);
+ LockRelationOid(indexoid, AccessShareLock);
+ ird = RelationBuildDesc(indexoid, true);
+ if (ird == NULL)
+ elog(PANIC, "could not open critical system index %u", indexoid);
+ ird->rd_isnailed = true;
+ ird->rd_refcnt = 1;
+ UnlockRelationOid(indexoid, AccessShareLock);
+ UnlockRelationOid(heapoid, AccessShareLock);
+}
+
+/*
+ * GetPgClassDescriptor -- get a predefined tuple descriptor for pg_class
* GetPgIndexDescriptor -- get a predefined tuple descriptor for pg_index
*
* We need this kluge because we have to be able to access non-fixed-width
- * fields of pg_index before we have the standard catalog caches available.
- * We use predefined data that's set up in just the same way as the
- * bootstrapped reldescs used by formrdesc(). The resulting tupdesc is
- * not 100% kosher: it does not have the correct rowtype OID in tdtypeid,
- * nor does it have a TupleConstr field. But it's good enough for the
- * purpose of extracting fields.
+ * fields of pg_class and pg_index before we have the standard catalog caches
+ * available. We use predefined data that's set up in just the same way as
+ * the bootstrapped reldescs used by formrdesc(). The resulting tupdesc is
+ * not 100% kosher: it does not have the correct rowtype OID in tdtypeid, nor
+ * does it have a TupleConstr field. But it's good enough for the purpose of
+ * extracting fields.
*/
static TupleDesc
-GetPgIndexDescriptor(void)
+BuildHardcodedDescriptor(int natts, const FormData_pg_attribute *attrs,
+ bool hasoids)
{
- static TupleDesc pgindexdesc = NULL;
+ TupleDesc result;
MemoryContext oldcxt;
int i;
- /* Already done? */
- if (pgindexdesc)
- return pgindexdesc;
-
oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
- pgindexdesc = CreateTemplateTupleDesc(Natts_pg_index, false);
- pgindexdesc->tdtypeid = RECORDOID; /* not right, but we don't care */
- pgindexdesc->tdtypmod = -1;
+ result = CreateTemplateTupleDesc(natts, hasoids);
+ result->tdtypeid = RECORDOID; /* not right, but we don't care */
+ result->tdtypmod = -1;
- for (i = 0; i < Natts_pg_index; i++)
+ for (i = 0; i < natts; i++)
{
- memcpy(pgindexdesc->attrs[i],
- &Desc_pg_index[i],
- ATTRIBUTE_TUPLE_SIZE);
+ memcpy(result->attrs[i], &attrs[i], ATTRIBUTE_FIXED_PART_SIZE);
/* make sure attcacheoff is valid */
- pgindexdesc->attrs[i]->attcacheoff = -1;
+ result->attrs[i]->attcacheoff = -1;
}
/* initialize first attribute's attcacheoff, cf RelationBuildTupleDesc */
- pgindexdesc->attrs[0]->attcacheoff = 0;
+ result->attrs[0]->attcacheoff = 0;
/* Note: we don't bother to set up a TupleConstr entry */
MemoryContextSwitchTo(oldcxt);
+ return result;
+}
+
+static TupleDesc
+GetPgClassDescriptor(void)
+{
+ static TupleDesc pgclassdesc = NULL;
+
+ /* Already done? */
+ if (pgclassdesc == NULL)
+ pgclassdesc = BuildHardcodedDescriptor(Natts_pg_class,
+ Desc_pg_class,
+ true);
+
+ return pgclassdesc;
+}
+
+static TupleDesc
+GetPgIndexDescriptor(void)
+{
+ static TupleDesc pgindexdesc = NULL;
+
+ /* Already done? */
+ if (pgindexdesc == NULL)
+ pgindexdesc = BuildHardcodedDescriptor(Natts_pg_index,
+ Desc_pg_index,
+ false);
+
return pgindexdesc;
}
+/*
+ * Load any default attribute value definitions for the relation.
+ */
static void
AttrDefaultFetch(Relation relation)
{
continue;
if (attrdef[i].adbin != NULL)
elog(WARNING, "multiple attrdef records found for attr %s of rel %s",
- NameStr(relation->rd_att->attrs[adform->adnum - 1]->attname),
+ NameStr(relation->rd_att->attrs[adform->adnum - 1]->attname),
RelationGetRelationName(relation));
else
found++;
adrel->rd_att, &isnull);
if (isnull)
elog(WARNING, "null adbin for attr %s of rel %s",
- NameStr(relation->rd_att->attrs[adform->adnum - 1]->attname),
+ NameStr(relation->rd_att->attrs[adform->adnum - 1]->attname),
RelationGetRelationName(relation));
else
attrdef[i].adbin = MemoryContextStrdup(CacheMemoryContext,
- DatumGetCString(DirectFunctionCall1(textout,
- val)));
+ TextDatumGetCString(val));
break;
}
ndef - found, RelationGetRelationName(relation));
}
+/*
+ * Load any check constraints for the relation.
+ */
static void
CheckConstraintFetch(Relation relation)
{
RelationGetRelationName(relation));
check[found].ccname = MemoryContextStrdup(CacheMemoryContext,
- NameStr(conform->conname));
+ NameStr(conform->conname));
/* Grab and test conbin is actually set */
val = fastgetattr(htup,
RelationGetRelationName(relation));
check[found].ccbin = MemoryContextStrdup(CacheMemoryContext,
- DatumGetCString(DirectFunctionCall1(textout,
- val)));
+ TextDatumGetCString(val));
found++;
}
*
* Since shared cache inval causes the relcache's copy of the list to go away,
* we return a copy of the list palloc'd in the caller's context. The caller
- * may freeList() the returned list after scanning it. This is necessary
+ * may list_free() the returned list after scanning it. This is necessary
* since the caller will typically be doing syscache lookups on the relevant
* indexes, and syscache lookup could cause SI messages to be processed!
*
return list_copy(relation->rd_indexlist);
/*
- * We build the list we intend to return (in the caller's context)
- * while doing the scan. After successfully completing the scan, we
- * copy that list into the relcache entry. This avoids cache-context
- * memory leakage if we get some sort of error partway through.
+ * We build the list we intend to return (in the caller's context) while
+ * doing the scan. After successfully completing the scan, we copy that
+ * list into the relcache entry. This avoids cache-context memory leakage
+ * if we get some sort of error partway through.
*/
result = NIL;
oidIndex = InvalidOid;
/* Check to see if it is a unique, non-partial btree index on OID */
if (index->indnatts == 1 &&
- index->indisunique &&
+ index->indisunique && index->indimmediate &&
index->indkey.values[0] == ObjectIdAttributeNumber &&
index->indclass.values[0] == OID_BTREE_OPS_OID &&
heap_attisnull(htup, Anum_pg_index_indpred))
* messages. In practice it is only used on pg_class (see REINDEX).
*
* It is up to the caller to make sure the given list is correctly ordered.
+ *
+ * We deliberately do not change rd_indexattr here: even when operating
+ * with a temporary partial index list, HOT-update decisions must be made
+ * correctly with respect to the full index set. It is up to the caller
+ * to ensure that a correct rd_indexattr set has been cached before first
+ * calling RelationSetIndexList; else a subsequent inquiry might cause a
+ * wrong rd_indexattr set to get computed and cached.
*/
void
RelationSetIndexList(Relation relation, List *indexIds, Oid oidIndex)
List *ilist;
/*
- * If relation doesn't have OIDs at all, caller is probably confused.
- * (We could just silently return InvalidOid, but it seems better to
- * throw an assertion.)
+ * If relation doesn't have OIDs at all, caller is probably confused. (We
+ * could just silently return InvalidOid, but it seems better to throw an
+ * assertion.)
*/
Assert(relation->rd_rel->relhasoids);
return NIL;
/*
- * We build the tree we intend to return in the caller's context.
- * After successfully completing the work, we copy it into the
- * relcache entry. This avoids problems if we get some sort of error
- * partway through.
+ * We build the tree we intend to return in the caller's context. After
+ * successfully completing the work, we copy it into the relcache entry.
+ * This avoids problems if we get some sort of error partway through.
*/
exprsDatum = heap_getattr(relation->rd_indextuple,
Anum_pg_index_indexprs,
GetPgIndexDescriptor(),
&isnull);
Assert(!isnull);
- exprsString = DatumGetCString(DirectFunctionCall1(textout, exprsDatum));
+ exprsString = TextDatumGetCString(exprsDatum);
result = (List *) stringToNode(exprsString);
pfree(exprsString);
* them to similarly-processed qual clauses, and may fail to detect valid
* matches without this. We don't bother with canonicalize_qual, however.
*/
- result = (List *) eval_const_expressions((Node *) result);
+ result = (List *) eval_const_expressions(NULL, (Node *) result);
/*
* Also mark any coercion format fields as "don't care", so that the
fix_opfuncids((Node *) result);
/* Now save a copy of the completed tree in the relcache entry. */
- oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ oldcxt = MemoryContextSwitchTo(relation->rd_indexcxt);
relation->rd_indexprs = (List *) copyObject(result);
MemoryContextSwitchTo(oldcxt);
return NIL;
/*
- * We build the tree we intend to return in the caller's context.
- * After successfully completing the work, we copy it into the
- * relcache entry. This avoids problems if we get some sort of error
- * partway through.
+ * We build the tree we intend to return in the caller's context. After
+ * successfully completing the work, we copy it into the relcache entry.
+ * This avoids problems if we get some sort of error partway through.
*/
predDatum = heap_getattr(relation->rd_indextuple,
Anum_pg_index_indpred,
GetPgIndexDescriptor(),
&isnull);
Assert(!isnull);
- predString = DatumGetCString(DirectFunctionCall1(textout, predDatum));
+ predString = TextDatumGetCString(predDatum);
result = (List *) stringToNode(predString);
pfree(predString);
* 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
- * index predicates.)
+ * stuff involving subqueries, however, since we don't allow any in index
+ * predicates.)
*/
- result = (List *) eval_const_expressions((Node *) result);
+ result = (List *) eval_const_expressions(NULL, (Node *) result);
result = (List *) canonicalize_qual((Expr *) result);
fix_opfuncids((Node *) result);
/* Now save a copy of the completed tree in the relcache entry. */
- oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ oldcxt = MemoryContextSwitchTo(relation->rd_indexcxt);
relation->rd_indpred = (List *) copyObject(result);
MemoryContextSwitchTo(oldcxt);
return result;
}
+/*
+ * RelationGetIndexAttrBitmap -- get a bitmap of index attribute numbers
+ *
+ * The result has a bit set for each attribute used anywhere in the index
+ * definitions of all the indexes on this relation. (This includes not only
+ * simple index keys, but attributes used in expressions and partial-index
+ * predicates.)
+ *
+ * Attribute numbers are offset by FirstLowInvalidHeapAttributeNumber so that
+ * we can include system attributes (e.g., OID) in the bitmap representation.
+ *
+ * The returned result is palloc'd in the caller's memory context and should
+ * be bms_free'd when not needed anymore.
+ */
+Bitmapset *
+RelationGetIndexAttrBitmap(Relation relation)
+{
+ Bitmapset *indexattrs;
+ List *indexoidlist;
+ ListCell *l;
+ MemoryContext oldcxt;
+
+ /* Quick exit if we already computed the result. */
+ if (relation->rd_indexattr != NULL)
+ return bms_copy(relation->rd_indexattr);
+
+ /* Fast path if definitely no indexes */
+ if (!RelationGetForm(relation)->relhasindex)
+ return NULL;
+
+ /*
+ * Get cached list of index OIDs
+ */
+ indexoidlist = RelationGetIndexList(relation);
+
+ /* Fall out if no indexes (but relhasindex was set) */
+ if (indexoidlist == NIL)
+ return NULL;
+
+ /*
+ * For each index, add referenced attributes to indexattrs.
+ */
+ indexattrs = NULL;
+ foreach(l, indexoidlist)
+ {
+ Oid indexOid = lfirst_oid(l);
+ Relation indexDesc;
+ IndexInfo *indexInfo;
+ int i;
+
+ indexDesc = index_open(indexOid, AccessShareLock);
+
+ /* Extract index key information from the index's pg_index row */
+ indexInfo = BuildIndexInfo(indexDesc);
+
+ /* Collect simple attribute references */
+ for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
+ {
+ int attrnum = indexInfo->ii_KeyAttrNumbers[i];
+
+ if (attrnum != 0)
+ indexattrs = bms_add_member(indexattrs,
+ attrnum - FirstLowInvalidHeapAttributeNumber);
+ }
+
+ /* Collect all attributes used in expressions, too */
+ pull_varattnos((Node *) indexInfo->ii_Expressions, &indexattrs);
+
+ /* Collect all attributes in the index predicate, too */
+ pull_varattnos((Node *) indexInfo->ii_Predicate, &indexattrs);
+
+ index_close(indexDesc, AccessShareLock);
+ }
+
+ list_free(indexoidlist);
+
+ /* Now save a copy of the bitmap in the relcache entry. */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ relation->rd_indexattr = bms_copy(indexattrs);
+ MemoryContextSwitchTo(oldcxt);
+
+ /* We return our original working copy for caller to play with */
+ return indexattrs;
+}
+
+/*
+ * RelationGetExclusionInfo -- get info about index's exclusion constraint
+ *
+ * This should be called only for an index that is known to have an
+ * associated exclusion constraint. It returns arrays (palloc'd in caller's
+ * context) of the exclusion operator OIDs, their underlying functions'
+ * OIDs, and their strategy numbers in the index's opclasses. We cache
+ * all this information since it requires a fair amount of work to get.
+ */
+void
+RelationGetExclusionInfo(Relation indexRelation,
+ Oid **operators,
+ Oid **procs,
+ uint16 **strategies)
+{
+ int ncols = indexRelation->rd_rel->relnatts;
+ Oid *ops;
+ Oid *funcs;
+ uint16 *strats;
+ Relation conrel;
+ SysScanDesc conscan;
+ ScanKeyData skey[1];
+ HeapTuple htup;
+ bool found;
+ MemoryContext oldcxt;
+ int i;
+
+ /* Allocate result space in caller context */
+ *operators = ops = (Oid *) palloc(sizeof(Oid) * ncols);
+ *procs = funcs = (Oid *) palloc(sizeof(Oid) * ncols);
+ *strategies = strats = (uint16 *) palloc(sizeof(uint16) * ncols);
+
+ /* Quick exit if we have the data cached already */
+ if (indexRelation->rd_exclstrats != NULL)
+ {
+ memcpy(ops, indexRelation->rd_exclops, sizeof(Oid) * ncols);
+ memcpy(funcs, indexRelation->rd_exclprocs, sizeof(Oid) * ncols);
+ memcpy(strats, indexRelation->rd_exclstrats, sizeof(uint16) * ncols);
+ return;
+ }
+
+ /*
+ * Search pg_constraint for the constraint associated with the index. To
+ * make this not too painfully slow, we use the index on conrelid; that
+ * will hold the parent relation's OID not the index's own OID.
+ */
+ ScanKeyInit(&skey[0],
+ Anum_pg_constraint_conrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(indexRelation->rd_index->indrelid));
+
+ conrel = heap_open(ConstraintRelationId, AccessShareLock);
+ conscan = systable_beginscan(conrel, ConstraintRelidIndexId, true,
+ SnapshotNow, 1, skey);
+ found = false;
+
+ while (HeapTupleIsValid(htup = systable_getnext(conscan)))
+ {
+ Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(htup);
+ Datum val;
+ bool isnull;
+ ArrayType *arr;
+ int nelem;
+
+ /* We want the exclusion constraint owning the index */
+ if (conform->contype != CONSTRAINT_EXCLUSION ||
+ conform->conindid != RelationGetRelid(indexRelation))
+ continue;
+
+ /* There should be only one */
+ if (found)
+ elog(ERROR, "unexpected exclusion constraint record found for rel %s",
+ RelationGetRelationName(indexRelation));
+ found = true;
+
+ /* Extract the operator OIDS from conexclop */
+ val = fastgetattr(htup,
+ Anum_pg_constraint_conexclop,
+ conrel->rd_att, &isnull);
+ if (isnull)
+ elog(ERROR, "null conexclop for rel %s",
+ RelationGetRelationName(indexRelation));
+
+ arr = DatumGetArrayTypeP(val); /* ensure not toasted */
+ nelem = ARR_DIMS(arr)[0];
+ if (ARR_NDIM(arr) != 1 ||
+ nelem != ncols ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != OIDOID)
+ elog(ERROR, "conexclop is not a 1-D Oid array");
+
+ memcpy(ops, ARR_DATA_PTR(arr), sizeof(Oid) * ncols);
+ }
+
+ systable_endscan(conscan);
+ heap_close(conrel, AccessShareLock);
+
+ if (!found)
+ elog(ERROR, "exclusion constraint record missing for rel %s",
+ RelationGetRelationName(indexRelation));
+
+ /* We need the func OIDs and strategy numbers too */
+ for (i = 0; i < ncols; i++)
+ {
+ funcs[i] = get_opcode(ops[i]);
+ strats[i] = get_op_opfamily_strategy(ops[i],
+ indexRelation->rd_opfamily[i]);
+ /* shouldn't fail, since it was checked at index creation */
+ if (strats[i] == InvalidStrategy)
+ elog(ERROR, "could not find strategy for operator %u in family %u",
+ ops[i], indexRelation->rd_opfamily[i]);
+ }
+
+ /* Save a copy of the results in the relcache entry. */
+ oldcxt = MemoryContextSwitchTo(indexRelation->rd_indexcxt);
+ indexRelation->rd_exclops = (Oid *) palloc(sizeof(Oid) * ncols);
+ indexRelation->rd_exclprocs = (Oid *) palloc(sizeof(Oid) * ncols);
+ indexRelation->rd_exclstrats = (uint16 *) palloc(sizeof(uint16) * ncols);
+ memcpy(indexRelation->rd_exclops, ops, sizeof(Oid) * ncols);
+ memcpy(indexRelation->rd_exclprocs, funcs, sizeof(Oid) * ncols);
+ memcpy(indexRelation->rd_exclstrats, strats, sizeof(uint16) * ncols);
+ MemoryContextSwitchTo(oldcxt);
+}
+
/*
* load_relcache_init_file, write_relcache_init_file
* relation descriptors using sequential scans and write 'em to
* the initialization file for use by subsequent backends.
*
- * We could dispense with the initialization file and just build the
+ * As of Postgres 9.0, there is one local initialization file in each
+ * database, plus one shared initialization file for shared catalogs.
+ *
+ * We could dispense with the initialization files and just build the
* critical reldescs the hard way on every backend startup, but that
* slows down backend startup noticeably.
*
* just the ones that are absolutely critical; this allows us to speed
* up backend startup by not having to build such entries the hard way.
* Presently, all the catalog and index entries that are referred to
- * by catcaches are stored in the initialization file.
+ * by catcaches are stored in the initialization files.
*
* The same mechanism that detects when catcache and relcache entries
* need to be invalidated (due to catalog updates) also arranges to
- * unlink the initialization file when its contents may be out of date.
- * The file will then be rebuilt during the next backend startup.
+ * unlink the initialization files when the contents may be out of date.
+ * The files will then be rebuilt during the next backend startup.
*/
/*
- * load_relcache_init_file -- attempt to load cache from the init file
+ * load_relcache_init_file -- attempt to load cache from the shared
+ * or local cache init file
*
- * If successful, return TRUE and set criticalRelcachesBuilt to true.
- * If not successful, return FALSE and set needNewCacheFile to true.
+ * If successful, return TRUE and set criticalRelcachesBuilt or
+ * criticalSharedRelcachesBuilt to true.
+ * If not successful, return FALSE.
*
* NOTE: we assume we are already switched into CacheMemoryContext.
*/
static bool
-load_relcache_init_file(void)
+load_relcache_init_file(bool shared)
{
FILE *fp;
char initfilename[MAXPGPATH];
magic;
int i;
- snprintf(initfilename, sizeof(initfilename), "%s/%s",
- DatabasePath, RELCACHE_INIT_FILENAME);
+ if (shared)
+ snprintf(initfilename, sizeof(initfilename), "global/%s",
+ RELCACHE_INIT_FILENAME);
+ else
+ snprintf(initfilename, sizeof(initfilename), "%s/%s",
+ DatabasePath, RELCACHE_INIT_FILENAME);
fp = AllocateFile(initfilename, PG_BINARY_R);
if (fp == NULL)
- {
- needNewCacheFile = true;
return false;
- }
/*
- * Read the index relcache entries from the file. Note we will not
- * enter any of them into the cache if the read fails partway through;
- * this helps to guard against broken init files.
+ * Read the index relcache entries from the file. Note we will not enter
+ * any of them into the cache if the read fails partway through; this
+ * helps to guard against broken init files.
*/
max_rels = 100;
rels = (Relation *) palloc(max_rels * sizeof(Relation));
num_rels = 0;
nailed_rels = nailed_indexes = 0;
- initFileRelationIds = NIL;
/* check for correct magic number (compatible version) */
if (fread(&magic, 1, sizeof(magic), fp) != sizeof(magic))
Relation rel;
Form_pg_class relform;
bool has_not_null;
- Datum indclassDatum;
- bool isnull;
/* first read the relation descriptor length */
- if ((nread = fread(&len, 1, sizeof(len), fp)) != sizeof(len))
+ nread = fread(&len, 1, sizeof(len), fp);
+ if (nread != sizeof(len))
{
if (nread == 0)
break; /* end of file */
rel = rels[num_rels++] = (Relation) palloc(len);
/* then, read the Relation structure */
- if ((nread = fread(rel, 1, len, fp)) != len)
+ if (fread(rel, 1, len, fp) != len)
goto read_failed;
/* next read the relation tuple form */
- if ((nread = fread(&len, 1, sizeof(len), fp)) != sizeof(len))
+ if (fread(&len, 1, sizeof(len), fp) != sizeof(len))
goto read_failed;
relform = (Form_pg_class) palloc(len);
- if ((nread = fread(relform, 1, len, fp)) != len)
+ if (fread(relform, 1, len, fp) != len)
goto read_failed;
rel->rd_rel = relform;
/* initialize attribute tuple forms */
rel->rd_att = CreateTemplateTupleDesc(relform->relnatts,
relform->relhasoids);
+ rel->rd_att->tdrefcount = 1; /* mark as refcounted */
+
rel->rd_att->tdtypeid = relform->reltype;
rel->rd_att->tdtypmod = -1; /* unnecessary, but... */
has_not_null = false;
for (i = 0; i < relform->relnatts; i++)
{
- if ((nread = fread(&len, 1, sizeof(len), fp)) != sizeof(len))
+ if (fread(&len, 1, sizeof(len), fp) != sizeof(len))
goto read_failed;
- if (len != ATTRIBUTE_TUPLE_SIZE)
+ if (len != ATTRIBUTE_FIXED_PART_SIZE)
goto read_failed;
- if ((nread = fread(rel->rd_att->attrs[i], 1, len, fp)) != len)
+ if (fread(rel->rd_att->attrs[i], 1, len, fp) != len)
goto read_failed;
has_not_null |= rel->rd_att->attrs[i]->attnotnull;
}
+ /* next read the access method specific field */
+ if (fread(&len, 1, sizeof(len), fp) != sizeof(len))
+ goto read_failed;
+ if (len > 0)
+ {
+ rel->rd_options = palloc(len);
+ if (fread(rel->rd_options, 1, len, fp) != len)
+ goto read_failed;
+ if (len != VARSIZE(rel->rd_options))
+ goto read_failed; /* sanity check */
+ }
+ else
+ {
+ rel->rd_options = NULL;
+ }
+
/* mark not-null status */
if (has_not_null)
{
{
Form_pg_am am;
MemoryContext indexcxt;
- Oid *operator;
+ Oid *opfamily;
+ Oid *opcintype;
RegProcedure *support;
int nsupport;
+ int16 *indoption;
/* Count nailed indexes to ensure we have 'em all */
if (rel->rd_isnailed)
nailed_indexes++;
/* next, read the pg_index tuple */
- if ((nread = fread(&len, 1, sizeof(len), fp)) != sizeof(len))
+ if (fread(&len, 1, sizeof(len), fp) != sizeof(len))
goto read_failed;
rel->rd_indextuple = (HeapTuple) palloc(len);
- if ((nread = fread(rel->rd_indextuple, 1, len, fp)) != len)
+ if (fread(rel->rd_indextuple, 1, len, fp) != len)
goto read_failed;
/* Fix up internal pointers in the tuple -- see heap_copytuple */
- rel->rd_indextuple->t_datamcxt = CurrentMemoryContext;
rel->rd_indextuple->t_data = (HeapTupleHeader) ((char *) rel->rd_indextuple + HEAPTUPLESIZE);
rel->rd_index = (Form_pg_index) GETSTRUCT(rel->rd_indextuple);
- /* fix up indclass pointer too */
- indclassDatum = fastgetattr(rel->rd_indextuple,
- Anum_pg_index_indclass,
- GetPgIndexDescriptor(),
- &isnull);
- Assert(!isnull);
- rel->rd_indclass = (oidvector *) DatumGetPointer(indclassDatum);
-
/* next, read the access method tuple form */
- if ((nread = fread(&len, 1, sizeof(len), fp)) != sizeof(len))
+ if (fread(&len, 1, sizeof(len), fp) != sizeof(len))
goto read_failed;
am = (Form_pg_am) palloc(len);
- if ((nread = fread(am, 1, len, fp)) != len)
+ if (fread(am, 1, len, fp) != len)
goto read_failed;
rel->rd_am = am;
ALLOCSET_SMALL_MAXSIZE);
rel->rd_indexcxt = indexcxt;
- /* next, read the vector of operator OIDs */
- if ((nread = fread(&len, 1, sizeof(len), fp)) != sizeof(len))
+ /* next, read the vector of opfamily OIDs */
+ if (fread(&len, 1, sizeof(len), fp) != sizeof(len))
goto read_failed;
- operator = (Oid *) MemoryContextAlloc(indexcxt, len);
- if ((nread = fread(operator, 1, len, fp)) != len)
+ opfamily = (Oid *) MemoryContextAlloc(indexcxt, len);
+ if (fread(opfamily, 1, len, fp) != len)
goto read_failed;
- rel->rd_operator = operator;
+ rel->rd_opfamily = opfamily;
+
+ /* next, read the vector of opcintype OIDs */
+ if (fread(&len, 1, sizeof(len), fp) != sizeof(len))
+ goto read_failed;
- /* finally, read the vector of support procedures */
- if ((nread = fread(&len, 1, sizeof(len), fp)) != sizeof(len))
+ opcintype = (Oid *) MemoryContextAlloc(indexcxt, len);
+ if (fread(opcintype, 1, len, fp) != len)
+ goto read_failed;
+
+ rel->rd_opcintype = opcintype;
+
+ /* next, read the vector of support procedure OIDs */
+ if (fread(&len, 1, sizeof(len), fp) != sizeof(len))
goto read_failed;
support = (RegProcedure *) MemoryContextAlloc(indexcxt, len);
- if ((nread = fread(support, 1, len, fp)) != len)
+ if (fread(support, 1, len, fp) != len)
goto read_failed;
rel->rd_support = support;
+ /* finally, read the vector of indoption values */
+ if (fread(&len, 1, sizeof(len), fp) != sizeof(len))
+ goto read_failed;
+
+ indoption = (int16 *) MemoryContextAlloc(indexcxt, len);
+ if (fread(indoption, 1, len, fp) != len)
+ goto read_failed;
+
+ rel->rd_indoption = indoption;
+
/* set up zeroed fmgr-info vectors */
rel->rd_aminfo = (RelationAmInfo *)
MemoryContextAllocZero(indexcxt, sizeof(RelationAmInfo));
Assert(rel->rd_index == NULL);
Assert(rel->rd_indextuple == NULL);
- Assert(rel->rd_indclass == NULL);
Assert(rel->rd_am == NULL);
Assert(rel->rd_indexcxt == NULL);
Assert(rel->rd_aminfo == NULL);
- Assert(rel->rd_operator == NULL);
+ Assert(rel->rd_opfamily == NULL);
+ Assert(rel->rd_opcintype == NULL);
Assert(rel->rd_support == NULL);
Assert(rel->rd_supportinfo == NULL);
+ Assert(rel->rd_indoption == NULL);
}
/*
* Rules and triggers are not saved (mainly because the internal
- * format is complex and subject to change). They must be rebuilt
- * if needed by RelationCacheInitializePhase2. This is not
- * expected to be a big performance hit since few system catalogs
- * have such. Ditto for index expressions and predicates.
+ * format is complex and subject to change). They must be rebuilt if
+ * needed by RelationCacheInitializePhase3. This is not expected to
+ * be a big performance hit since few system catalogs have such. Ditto
+ * for index expressions, predicates, and exclusion info.
*/
rel->rd_rules = NULL;
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
+ rel->rd_exclops = NULL;
+ rel->rd_exclprocs = NULL;
+ rel->rd_exclstrats = NULL;
/*
* Reset transient-state fields in the relcache entry
*/
rel->rd_smgr = NULL;
- rel->rd_targblock = InvalidBlockNumber;
if (rel->rd_isnailed)
rel->rd_refcnt = 1;
else
rel->rd_refcnt = 0;
rel->rd_indexvalid = 0;
rel->rd_indexlist = NIL;
+ rel->rd_indexattr = NULL;
rel->rd_oidindex = InvalidOid;
rel->rd_createSubid = InvalidSubTransactionId;
+ rel->rd_newRelfilenodeSubid = InvalidSubTransactionId;
+ rel->rd_amcache = NULL;
MemSet(&rel->pgstat_info, 0, sizeof(rel->pgstat_info));
/*
* Recompute lock and physical addressing info. This is needed in
- * case the pg_internal.init file was copied from some other
- * database by CREATE DATABASE.
+ * case the pg_internal.init file was copied from some other database
+ * by CREATE DATABASE.
*/
RelationInitLockInfo(rel);
RelationInitPhysicalAddr(rel);
}
/*
- * We reached the end of the init file without apparent problem. Did
- * we get the right number of nailed items? (This is a useful
- * crosscheck in case the set of critical rels or indexes changes.)
+ * We reached the end of the init file without apparent problem. Did we
+ * get the right number of nailed items? (This is a useful crosscheck in
+ * case the set of critical rels or indexes changes.)
*/
- if (nailed_rels != NUM_CRITICAL_RELS ||
- nailed_indexes != NUM_CRITICAL_INDEXES)
- goto read_failed;
+ if (shared)
+ {
+ if (nailed_rels != NUM_CRITICAL_SHARED_RELS ||
+ nailed_indexes != NUM_CRITICAL_SHARED_INDEXES)
+ goto read_failed;
+ }
+ else
+ {
+ if (nailed_rels != NUM_CRITICAL_LOCAL_RELS ||
+ nailed_indexes != NUM_CRITICAL_LOCAL_INDEXES)
+ goto read_failed;
+ }
/*
* OK, all appears well.
{
RelationCacheInsert(rels[relno]);
/* also make a list of their OIDs, for RelationIdIsInInitFile */
- initFileRelationIds = lcons_oid(RelationGetRelid(rels[relno]),
- initFileRelationIds);
+ if (!shared)
+ initFileRelationIds = lcons_oid(RelationGetRelid(rels[relno]),
+ initFileRelationIds);
}
pfree(rels);
FreeFile(fp);
- criticalRelcachesBuilt = true;
+ if (shared)
+ criticalSharedRelcachesBuilt = true;
+ else
+ criticalRelcachesBuilt = true;
return true;
/*
- * init file is broken, so do it the hard way. We don't bother trying
- * to free the clutter we just allocated; it's not in the relcache so
- * it won't hurt.
+ * init file is broken, so do it the hard way. We don't bother trying to
+ * free the clutter we just allocated; it's not in the relcache so it
+ * won't hurt.
*/
read_failed:
pfree(rels);
FreeFile(fp);
- needNewCacheFile = true;
return false;
}
/*
* Write out a new initialization file with the current contents
- * of the relcache.
+ * of the relcache (either shared rels or local rels, as indicated).
*/
static void
-write_relcache_init_file(void)
+write_relcache_init_file(bool shared)
{
FILE *fp;
char tempfilename[MAXPGPATH];
/*
* We must write a temporary file and rename it into place. Otherwise,
- * another backend starting at about the same time might crash trying
- * to read the partially-complete file.
+ * another backend starting at about the same time might crash trying to
+ * read the partially-complete file.
*/
- snprintf(tempfilename, sizeof(tempfilename), "%s/%s.%d",
- DatabasePath, RELCACHE_INIT_FILENAME, MyProcPid);
- snprintf(finalfilename, sizeof(finalfilename), "%s/%s",
- DatabasePath, RELCACHE_INIT_FILENAME);
+ if (shared)
+ {
+ snprintf(tempfilename, sizeof(tempfilename), "global/%s.%d",
+ RELCACHE_INIT_FILENAME, MyProcPid);
+ snprintf(finalfilename, sizeof(finalfilename), "global/%s",
+ RELCACHE_INIT_FILENAME);
+ }
+ else
+ {
+ snprintf(tempfilename, sizeof(tempfilename), "%s/%s.%d",
+ DatabasePath, RELCACHE_INIT_FILENAME, MyProcPid);
+ snprintf(finalfilename, sizeof(finalfilename), "%s/%s",
+ DatabasePath, RELCACHE_INIT_FILENAME);
+ }
unlink(tempfilename); /* in case it exists w/wrong permissions */
(errcode_for_file_access(),
errmsg("could not create relation-cache initialization file \"%s\": %m",
tempfilename),
- errdetail("Continuing anyway, but there's something wrong.")));
+ errdetail("Continuing anyway, but there's something wrong.")));
return;
}
elog(FATAL, "could not write init file");
/*
- * Write all the reldescs (in no particular order).
+ * Write all the appropriate reldescs (in no particular order).
*/
hash_seq_init(&status, RelationIdCache);
- initFileRelationIds = NIL;
-
while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
{
Relation rel = idhentry->reldesc;
Form_pg_class relform = rel->rd_rel;
- Size len;
- /*
- * first write the relcache entry proper
- */
- len = sizeof(RelationData);
-
- /* first, write the relation descriptor length */
- if (fwrite(&len, 1, sizeof(len), fp) != sizeof(len))
- elog(FATAL, "could not write init file");
+ /* ignore if not correct group */
+ if (relform->relisshared != shared)
+ continue;
- /* next, write out the Relation structure */
- if (fwrite(rel, 1, len, fp) != len)
- elog(FATAL, "could not write init file");
+ /* first write the relcache entry proper */
+ write_item(rel, sizeof(RelationData), fp);
/* next write the relation tuple form */
- len = sizeof(FormData_pg_class);
- if (fwrite(&len, 1, sizeof(len), fp) != sizeof(len))
- elog(FATAL, "could not write init file");
-
- if (fwrite(relform, 1, len, fp) != len)
- elog(FATAL, "could not write init file");
+ write_item(relform, CLASS_TUPLE_SIZE, fp);
/* next, do all the attribute tuple form data entries */
for (i = 0; i < relform->relnatts; i++)
{
- len = ATTRIBUTE_TUPLE_SIZE;
- if (fwrite(&len, 1, sizeof(len), fp) != sizeof(len))
- elog(FATAL, "could not write init file");
- if (fwrite(rel->rd_att->attrs[i], 1, len, fp) != len)
- elog(FATAL, "could not write init file");
+ write_item(rel->rd_att->attrs[i], ATTRIBUTE_FIXED_PART_SIZE, fp);
}
+ /* next, do the access method specific field */
+ write_item(rel->rd_options,
+ (rel->rd_options ? VARSIZE(rel->rd_options) : 0),
+ fp);
+
/* If it's an index, there's more to do */
if (rel->rd_rel->relkind == RELKIND_INDEX)
{
/* write the pg_index tuple */
/* we assume this was created by heap_copytuple! */
- len = HEAPTUPLESIZE + rel->rd_indextuple->t_len;
- if (fwrite(&len, 1, sizeof(len), fp) != sizeof(len))
- elog(FATAL, "could not write init file");
-
- if (fwrite(rel->rd_indextuple, 1, len, fp) != len)
- elog(FATAL, "could not write init file");
+ write_item(rel->rd_indextuple,
+ HEAPTUPLESIZE + rel->rd_indextuple->t_len,
+ fp);
/* next, write the access method tuple form */
- len = sizeof(FormData_pg_am);
- if (fwrite(&len, 1, sizeof(len), fp) != sizeof(len))
- elog(FATAL, "could not write init file");
-
- if (fwrite(am, 1, len, fp) != len)
- elog(FATAL, "could not write init file");
-
- /* next, write the vector of operator OIDs */
- len = relform->relnatts * (am->amstrategies * sizeof(Oid));
- if (fwrite(&len, 1, sizeof(len), fp) != sizeof(len))
- elog(FATAL, "could not write init file");
-
- if (fwrite(rel->rd_operator, 1, len, fp) != len)
- elog(FATAL, "could not write init file");
-
- /* finally, write the vector of support procedures */
- len = relform->relnatts * (am->amsupport * sizeof(RegProcedure));
- if (fwrite(&len, 1, sizeof(len), fp) != sizeof(len))
- elog(FATAL, "could not write init file");
-
- if (fwrite(rel->rd_support, 1, len, fp) != len)
- elog(FATAL, "could not write init file");
+ write_item(am, sizeof(FormData_pg_am), fp);
+
+ /* next, write the vector of opfamily OIDs */
+ write_item(rel->rd_opfamily,
+ relform->relnatts * sizeof(Oid),
+ fp);
+
+ /* next, write the vector of opcintype OIDs */
+ write_item(rel->rd_opcintype,
+ relform->relnatts * sizeof(Oid),
+ fp);
+
+ /* next, write the vector of support procedure OIDs */
+ write_item(rel->rd_support,
+ relform->relnatts * (am->amsupport * sizeof(RegProcedure)),
+ fp);
+
+ /* finally, write the vector of indoption values */
+ write_item(rel->rd_indoption,
+ relform->relnatts * sizeof(int16),
+ fp);
}
/* also make a list of their OIDs, for RelationIdIsInInitFile */
- oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
- initFileRelationIds = lcons_oid(RelationGetRelid(rel),
- initFileRelationIds);
- MemoryContextSwitchTo(oldcxt);
+ if (!shared)
+ {
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ initFileRelationIds = lcons_oid(RelationGetRelid(rel),
+ initFileRelationIds);
+ MemoryContextSwitchTo(oldcxt);
+ }
}
if (FreeFile(fp))
/*
* Now we have to check whether the data we've so painstakingly
- * accumulated is already obsolete due to someone else's
- * just-committed catalog changes. If so, we just delete the temp
- * file and leave it to the next backend to try again. (Our own
- * relcache entries will be updated by SI message processing, but we
- * can't be sure whether what we wrote out was up-to-date.)
+ * accumulated is already obsolete due to someone else's just-committed
+ * catalog changes. If so, we just delete the temp file and leave it to
+ * the next backend to try again. (Our own relcache entries will be
+ * updated by SI message processing, but we can't be sure whether what we
+ * wrote out was up-to-date.)
*
* This mustn't run concurrently with RelationCacheInitFileInvalidate, so
* grab a serialization lock for the duration.
AcceptInvalidationMessages();
/*
- * If we have received any SI relcache invals since backend start,
- * assume we may have written out-of-date data.
+ * If we have received any SI relcache invals since backend start, assume
+ * we may have written out-of-date data.
*/
if (relcacheInvalsReceived == 0L)
{
* previously-existing init file.
*
* Note: a failure here is possible under Cygwin, if some other
- * backend is holding open an unlinked-but-not-yet-gone init file.
- * So treat this as a noncritical failure; just remove the useless
- * temp file on failure.
+ * backend is holding open an unlinked-but-not-yet-gone init file. So
+ * treat this as a noncritical failure; just remove the useless temp
+ * file on failure.
*/
if (rename(tempfilename, finalfilename) < 0)
unlink(tempfilename);
LWLockRelease(RelCacheInitLock);
}
+/* write a chunk of data preceded by its length */
+static void
+write_item(const void *data, Size len, FILE *fp)
+{
+ if (fwrite(&len, 1, sizeof(len), fp) != sizeof(len))
+ elog(FATAL, "could not write init file");
+ if (fwrite(data, 1, len, fp) != len)
+ elog(FATAL, "could not write init file");
+}
+
/*
* Detect whether a given relation (identified by OID) is one of the ones
- * we store in the init file.
+ * we store in the local relcache init file.
*
* Note that we effectively assume that all backends running in a database
* would choose to store the same set of relations in the init file;
/*
* Invalidate (remove) the init file during commit of a transaction that
* changed one or more of the relation cache entries that are kept in the
- * init file.
+ * local init file.
*
* We actually need to remove the init file twice: once just before sending
* the SI messages that include relcache inval for such relations, and once
* just after sending them. The unlink before ensures that a backend that's
* currently starting cannot read the now-obsolete init file and then miss
* the SI messages that will force it to update its relcache entries. (This
- * works because the backend startup sequence gets into the PROC array before
+ * works because the backend startup sequence gets into the PGPROC array before
* trying to load the init file.) The unlink after is to synchronize with a
* backend that may currently be trying to write an init file based on data
* that we've just rendered invalid. Such a backend will see the SI messages,
*
* Ignore any failure to unlink the file, since it might not be there if
* no backend has been started since the last removal.
+ *
+ * Notice this deals only with the local init file, not the shared init file.
+ * The reason is that there can never be a "significant" change to the
+ * relcache entry of a shared relation; the most that could happen is
+ * updates of noncritical fields such as relpages/reltuples. So, while
+ * it's worth updating the shared init file from time to time, it can never
+ * be invalid enough to make it necessary to remove it.
*/
void
RelationCacheInitFileInvalidate(bool beforeSend)
/*
* We need to interlock this against write_relcache_init_file, to
* guard against possibility that someone renames a new-but-
- * already-obsolete init file into place just after we unlink.
- * With the interlock, it's certain that write_relcache_init_file
- * will notice our SI inval message before renaming into place, or
- * else that we will execute second and successfully unlink the
- * file.
+ * already-obsolete init file into place just after we unlink. With
+ * the interlock, it's certain that write_relcache_init_file will
+ * notice our SI inval message before renaming into place, or else
+ * that we will execute second and successfully unlink the file.
*/
LWLockAcquire(RelCacheInitLock, LW_EXCLUSIVE);
unlink(initfilename);
LWLockRelease(RelCacheInitLock);
}
}
+
+/*
+ * Remove the init files during postmaster startup.
+ *
+ * We used to keep the init files across restarts, but that is unsafe in PITR
+ * scenarios, and even in simple crash-recovery cases there are windows for
+ * the init files to become out-of-sync with the database. So now we just
+ * remove them during startup and expect the first backend launch to rebuild
+ * them. Of course, this has to happen in each database of the cluster.
+ */
+void
+RelationCacheInitFileRemove(void)
+{
+ const char *tblspcdir = "pg_tblspc";
+ DIR *dir;
+ struct dirent *de;
+ char path[MAXPGPATH];
+
+ /*
+ * We zap the shared cache file too. In theory it can't get out of sync
+ * enough to be a problem, but in data-corruption cases, who knows ...
+ */
+ snprintf(path, sizeof(path), "global/%s",
+ RELCACHE_INIT_FILENAME);
+ unlink_initfile(path);
+
+ /* Scan everything in the default tablespace */
+ RelationCacheInitFileRemoveInDir("base");
+
+ /* Scan the tablespace link directory to find non-default tablespaces */
+ dir = AllocateDir(tblspcdir);
+ if (dir == NULL)
+ {
+ elog(LOG, "could not open tablespace link directory \"%s\": %m",
+ tblspcdir);
+ return;
+ }
+
+ while ((de = ReadDir(dir, tblspcdir)) != NULL)
+ {
+ if (strspn(de->d_name, "0123456789") == strlen(de->d_name))
+ {
+ /* Scan the tablespace dir for per-database dirs */
+ snprintf(path, sizeof(path), "%s/%s/%s",
+ tblspcdir, de->d_name, TABLESPACE_VERSION_DIRECTORY);
+ RelationCacheInitFileRemoveInDir(path);
+ }
+ }
+
+ FreeDir(dir);
+}
+
+/* Process one per-tablespace directory for RelationCacheInitFileRemove */
+static void
+RelationCacheInitFileRemoveInDir(const char *tblspcpath)
+{
+ DIR *dir;
+ struct dirent *de;
+ char initfilename[MAXPGPATH];
+
+ /* Scan the tablespace directory to find per-database directories */
+ dir = AllocateDir(tblspcpath);
+ if (dir == NULL)
+ {
+ elog(LOG, "could not open tablespace directory \"%s\": %m",
+ tblspcpath);
+ return;
+ }
+
+ while ((de = ReadDir(dir, tblspcpath)) != NULL)
+ {
+ if (strspn(de->d_name, "0123456789") == strlen(de->d_name))
+ {
+ /* Try to remove the init file in each database */
+ snprintf(initfilename, sizeof(initfilename), "%s/%s/%s",
+ tblspcpath, de->d_name, RELCACHE_INIT_FILENAME);
+ unlink_initfile(initfilename);
+ }
+ }
+
+ FreeDir(dir);
+}
+
+static void
+unlink_initfile(const char *initfilename)
+{
+ if (unlink(initfilename) < 0)
+ {
+ /* It might not be there, but log any error other than ENOENT */
+ if (errno != ENOENT)
+ elog(LOG, "could not remove cache file \"%s\": %m", initfilename);
+ }
+}