postgresql/src/backend/catalog/catalog.c

457 lines
13 KiB
C

/*-------------------------------------------------------------------------
*
* catalog.c
* routines concerned with catalog naming conventions and other
* bits of hard-wired knowledge
*
*
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/catalog/catalog.c,v 1.69 2007/01/05 22:19:24 momjian Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <fcntl.h>
#include <unistd.h>
#include "access/genam.h"
#include "access/transam.h"
#include "catalog/catalog.h"
#include "catalog/indexing.h"
#include "catalog/pg_auth_members.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_pltemplate.h"
#include "catalog/pg_shdepend.h"
#include "catalog/pg_shdescription.h"
#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "miscadmin.h"
#include "storage/fd.h"
#include "utils/fmgroids.h"
#include "utils/relcache.h"
#define OIDCHARS 10 /* max chars printed by %u */
/*
* relpath - construct path to a relation's file
*
* Result is a palloc'd string.
*/
char *
relpath(RelFileNode rnode)
{
int pathlen;
char *path;
if (rnode.spcNode == GLOBALTABLESPACE_OID)
{
/* Shared system relations live in {datadir}/global */
Assert(rnode.dbNode == 0);
pathlen = 7 + OIDCHARS + 1;
path = (char *) palloc(pathlen);
snprintf(path, pathlen, "global/%u",
rnode.relNode);
}
else if (rnode.spcNode == DEFAULTTABLESPACE_OID)
{
/* The default tablespace is {datadir}/base */
pathlen = 5 + OIDCHARS + 1 + OIDCHARS + 1;
path = (char *) palloc(pathlen);
snprintf(path, pathlen, "base/%u/%u",
rnode.dbNode, rnode.relNode);
}
else
{
/* All other tablespaces are accessed via symlinks */
pathlen = 10 + OIDCHARS + 1 + OIDCHARS + 1 + OIDCHARS + 1;
path = (char *) palloc(pathlen);
snprintf(path, pathlen, "pg_tblspc/%u/%u/%u",
rnode.spcNode, rnode.dbNode, rnode.relNode);
}
return path;
}
/*
* GetDatabasePath - construct path to a database dir
*
* Result is a palloc'd string.
*
* XXX this must agree with relpath()!
*/
char *
GetDatabasePath(Oid dbNode, Oid spcNode)
{
int pathlen;
char *path;
if (spcNode == GLOBALTABLESPACE_OID)
{
/* Shared system relations live in {datadir}/global */
Assert(dbNode == 0);
pathlen = 6 + 1;
path = (char *) palloc(pathlen);
snprintf(path, pathlen, "global");
}
else if (spcNode == DEFAULTTABLESPACE_OID)
{
/* The default tablespace is {datadir}/base */
pathlen = 5 + OIDCHARS + 1;
path = (char *) palloc(pathlen);
snprintf(path, pathlen, "base/%u",
dbNode);
}
else
{
/* All other tablespaces are accessed via symlinks */
pathlen = 10 + OIDCHARS + 1 + OIDCHARS + 1;
path = (char *) palloc(pathlen);
snprintf(path, pathlen, "pg_tblspc/%u/%u",
spcNode, dbNode);
}
return path;
}
/*
* IsSystemRelation
* True iff the relation is a system catalog relation.
*
* NB: TOAST relations are considered system relations by this test
* for compatibility with the old IsSystemRelationName function.
* This is appropriate in many places but not all. Where it's not,
* also check IsToastRelation.
*
* We now just test if the relation is in the system catalog namespace;
* so it's no longer necessary to forbid user relations from having
* names starting with pg_.
*/
bool
IsSystemRelation(Relation relation)
{
return IsSystemNamespace(RelationGetNamespace(relation)) ||
IsToastNamespace(RelationGetNamespace(relation));
}
/*
* IsSystemClass
* Like the above, but takes a Form_pg_class as argument.
* Used when we do not want to open the relation and have to
* search pg_class directly.
*/
bool
IsSystemClass(Form_pg_class reltuple)
{
Oid relnamespace = reltuple->relnamespace;
return IsSystemNamespace(relnamespace) ||
IsToastNamespace(relnamespace);
}
/*
* IsToastRelation
* True iff relation is a TOAST support relation (or index).
*/
bool
IsToastRelation(Relation relation)
{
return IsToastNamespace(RelationGetNamespace(relation));
}
/*
* IsToastClass
* Like the above, but takes a Form_pg_class as argument.
* Used when we do not want to open the relation and have to
* search pg_class directly.
*/
bool
IsToastClass(Form_pg_class reltuple)
{
Oid relnamespace = reltuple->relnamespace;
return IsToastNamespace(relnamespace);
}
/*
* IsSystemNamespace
* True iff namespace is pg_catalog.
*
* NOTE: the reason this isn't a macro is to avoid having to include
* catalog/pg_namespace.h in a lot of places.
*/
bool
IsSystemNamespace(Oid namespaceId)
{
return namespaceId == PG_CATALOG_NAMESPACE;
}
/*
* IsToastNamespace
* True iff namespace is pg_toast.
*
* NOTE: the reason this isn't a macro is to avoid having to include
* catalog/pg_namespace.h in a lot of places.
*/
bool
IsToastNamespace(Oid namespaceId)
{
return namespaceId == PG_TOAST_NAMESPACE;
}
/*
* IsReservedName
* True iff name starts with the pg_ prefix.
*
* For some classes of objects, the prefix pg_ is reserved for
* system objects only. As of 8.0, this is only true for
* schema and tablespace names.
*/
bool
IsReservedName(const char *name)
{
/* ugly coding for speed */
return (name[0] == 'p' &&
name[1] == 'g' &&
name[2] == '_');
}
/*
* IsSharedRelation
* Given the OID of a relation, determine whether it's supposed to be
* shared across an entire database cluster.
*
* Hard-wiring this list is pretty grotty, but we really need it so that
* we can compute the locktag for a relation (and then lock it) without
* having already read its pg_class entry. If we try to retrieve relisshared
* from pg_class with no pre-existing lock, there is a race condition against
* anyone who is concurrently committing a change to the pg_class entry:
* since we read system catalog entries under SnapshotNow, it's possible
* that both the old and new versions of the row are invalid at the instants
* we scan them. We fix this by insisting that updaters of a pg_class
* row must hold exclusive lock on the corresponding rel, and that users
* of a relation must hold at least AccessShareLock on the rel *before*
* trying to open its relcache entry. But to lock a rel, you have to
* know if it's shared. Fortunately, the set of shared relations is
* fairly static, so a hand-maintained list of their OIDs isn't completely
* impractical.
*/
bool
IsSharedRelation(Oid relationId)
{
/* These are the shared catalogs (look for BKI_SHARED_RELATION) */
if (relationId == AuthIdRelationId ||
relationId == AuthMemRelationId ||
relationId == DatabaseRelationId ||
relationId == PLTemplateRelationId ||
relationId == SharedDescriptionRelationId ||
relationId == SharedDependRelationId ||
relationId == TableSpaceRelationId)
return true;
/* These are their indexes (see indexing.h) */
if (relationId == AuthIdRolnameIndexId ||
relationId == AuthIdOidIndexId ||
relationId == AuthMemRoleMemIndexId ||
relationId == AuthMemMemRoleIndexId ||
relationId == DatabaseNameIndexId ||
relationId == DatabaseOidIndexId ||
relationId == PLTemplateNameIndexId ||
relationId == SharedDescriptionObjIndexId ||
relationId == SharedDependDependerIndexId ||
relationId == SharedDependReferenceIndexId ||
relationId == TablespaceOidIndexId ||
relationId == TablespaceNameIndexId)
return true;
/* These are their toast tables and toast indexes (see toasting.h) */
if (relationId == PgAuthidToastTable ||
relationId == PgAuthidToastIndex ||
relationId == PgDatabaseToastTable ||
relationId == PgDatabaseToastIndex ||
relationId == PgShdescriptionToastTable ||
relationId == PgShdescriptionToastIndex)
return true;
return false;
}
/*
* GetNewOid
* Generate a new OID that is unique within the given relation.
*
* Caller must have a suitable lock on the relation.
*
* Uniqueness is promised only if the relation has a unique index on OID.
* This is true for all system catalogs that have OIDs, but might not be
* true for user tables. Note that we are effectively assuming that the
* table has a relatively small number of entries (much less than 2^32)
* and there aren't very long runs of consecutive existing OIDs. Again,
* this is reasonable for system catalogs but less so for user tables.
*
* Since the OID is not immediately inserted into the table, there is a
* race condition here; but a problem could occur only if someone else
* managed to cycle through 2^32 OIDs and generate the same OID before we
* finish inserting our row. This seems unlikely to be a problem. Note
* that if we had to *commit* the row to end the race condition, the risk
* would be rather higher; therefore we use SnapshotDirty in the test,
* so that we will see uncommitted rows.
*/
Oid
GetNewOid(Relation relation)
{
Oid newOid;
Oid oidIndex;
Relation indexrel;
/* If relation doesn't have OIDs at all, caller is confused */
Assert(relation->rd_rel->relhasoids);
/* In bootstrap mode, we don't have any indexes to use */
if (IsBootstrapProcessingMode())
return GetNewObjectId();
/* The relcache will cache the identity of the OID index for us */
oidIndex = RelationGetOidIndex(relation);
/* If no OID index, just hand back the next OID counter value */
if (!OidIsValid(oidIndex))
{
/*
* System catalogs that have OIDs should *always* have a unique OID
* index; we should only take this path for user tables. Give a
* warning if it looks like somebody forgot an index.
*/
if (IsSystemRelation(relation))
elog(WARNING, "generating possibly-non-unique OID for \"%s\"",
RelationGetRelationName(relation));
return GetNewObjectId();
}
/* Otherwise, use the index to find a nonconflicting OID */
indexrel = index_open(oidIndex, AccessShareLock);
newOid = GetNewOidWithIndex(relation, indexrel);
index_close(indexrel, AccessShareLock);
return newOid;
}
/*
* GetNewOidWithIndex
* Guts of GetNewOid: use the supplied index
*
* This is exported separately because there are cases where we want to use
* an index that will not be recognized by RelationGetOidIndex: TOAST tables
* and pg_largeobject have indexes that are usable, but have multiple columns
* and are on ordinary columns rather than a true OID column. This code
* will work anyway, so long as the OID is the index's first column.
*
* Caller must have a suitable lock on the relation.
*/
Oid
GetNewOidWithIndex(Relation relation, Relation indexrel)
{
Oid newOid;
IndexScanDesc scan;
ScanKeyData key;
bool collides;
/* Generate new OIDs until we find one not in the table */
do
{
newOid = GetNewObjectId();
ScanKeyInit(&key,
(AttrNumber) 1,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(newOid));
/* see notes above about using SnapshotDirty */
scan = index_beginscan(relation, indexrel,
SnapshotDirty, 1, &key);
collides = HeapTupleIsValid(index_getnext(scan, ForwardScanDirection));
index_endscan(scan);
} while (collides);
return newOid;
}
/*
* GetNewRelFileNode
* Generate a new relfilenode number that is unique within the given
* tablespace.
*
* If the relfilenode will also be used as the relation's OID, pass the
* opened pg_class catalog, and this routine will guarantee that the result
* is also an unused OID within pg_class. If the result is to be used only
* as a relfilenode for an existing relation, pass NULL for pg_class.
*
* As with GetNewOid, there is some theoretical risk of a race condition,
* but it doesn't seem worth worrying about.
*
* Note: we don't support using this in bootstrap mode. All relations
* created by bootstrap have preassigned OIDs, so there's no need.
*/
Oid
GetNewRelFileNode(Oid reltablespace, bool relisshared, Relation pg_class)
{
RelFileNode rnode;
char *rpath;
int fd;
bool collides;
/* This should match RelationInitPhysicalAddr */
rnode.spcNode = reltablespace ? reltablespace : MyDatabaseTableSpace;
rnode.dbNode = relisshared ? InvalidOid : MyDatabaseId;
do
{
/* Generate the OID */
if (pg_class)
rnode.relNode = GetNewOid(pg_class);
else
rnode.relNode = GetNewObjectId();
/* Check for existing file of same name */
rpath = relpath(rnode);
fd = BasicOpenFile(rpath, O_RDONLY | PG_BINARY, 0);
if (fd >= 0)
{
/* definite collision */
close(fd);
collides = true;
}
else
{
/*
* Here we have a little bit of a dilemma: if errno is something
* other than ENOENT, should we declare a collision and loop? In
* particular one might think this advisable for, say, EPERM.
* However there really shouldn't be any unreadable files in a
* tablespace directory, and if the EPERM is actually complaining
* that we can't read the directory itself, we'd be in an infinite
* loop. In practice it seems best to go ahead regardless of the
* errno. If there is a colliding file we will get an smgr
* failure when we attempt to create the new relation file.
*/
collides = false;
}
pfree(rpath);
} while (collides);
return rnode.relNode;
}