diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:19:15 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:19:15 +0000 |
commit | 6eb9c5a5657d1fe77b55cc261450f3538d35a94d (patch) | |
tree | 657d8194422a5daccecfd42d654b8a245ef7b4c8 /src/backend/catalog/catalog.c | |
parent | Initial commit. (diff) | |
download | postgresql-13-6eb9c5a5657d1fe77b55cc261450f3538d35a94d.tar.xz postgresql-13-6eb9c5a5657d1fe77b55cc261450f3538d35a94d.zip |
Adding upstream version 13.4.upstream/13.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/backend/catalog/catalog.c')
-rw-r--r-- | src/backend/catalog/catalog.c | 533 |
1 files changed, 533 insertions, 0 deletions
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c new file mode 100644 index 0000000..7d6acae --- /dev/null +++ b/src/backend/catalog/catalog.c @@ -0,0 +1,533 @@ +/*------------------------------------------------------------------------- + * + * catalog.c + * routines concerned with catalog naming conventions and other + * bits of hard-wired knowledge + * + * + * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/catalog/catalog.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <fcntl.h> +#include <unistd.h> + +#include "access/genam.h" +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "access/table.h" +#include "access/transam.h" +#include "catalog/catalog.h" +#include "catalog/indexing.h" +#include "catalog/namespace.h" +#include "catalog/pg_auth_members.h" +#include "catalog/pg_authid.h" +#include "catalog/pg_database.h" +#include "catalog/pg_db_role_setting.h" +#include "catalog/pg_namespace.h" +#include "catalog/pg_replication_origin.h" +#include "catalog/pg_shdepend.h" +#include "catalog/pg_shdescription.h" +#include "catalog/pg_shseclabel.h" +#include "catalog/pg_subscription.h" +#include "catalog/pg_tablespace.h" +#include "catalog/pg_type.h" +#include "catalog/toasting.h" +#include "miscadmin.h" +#include "storage/fd.h" +#include "utils/fmgroids.h" +#include "utils/fmgrprotos.h" +#include "utils/rel.h" +#include "utils/snapmgr.h" +#include "utils/syscache.h" + +/* + * IsSystemRelation + * True iff the relation is either a system catalog or a toast table. + * See IsCatalogRelation for the exact definition of a system catalog. + * + * We treat toast tables of user relations as "system relations" for + * protection purposes, e.g. you can't change their schemas without + * special permissions. Therefore, most uses of this function are + * checking whether allow_system_table_mods restrictions apply. + * For other purposes, consider whether you shouldn't be using + * IsCatalogRelation instead. + * + * This function does not perform any catalog accesses. + * Some callers rely on that! + */ +bool +IsSystemRelation(Relation relation) +{ + return IsSystemClass(RelationGetRelid(relation), relation->rd_rel); +} + +/* + * 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(Oid relid, Form_pg_class reltuple) +{ + /* IsCatalogRelationOid is a bit faster, so test that first */ + return (IsCatalogRelationOid(relid) || IsToastClass(reltuple)); +} + +/* + * IsCatalogRelation + * True iff the relation is a system catalog. + * + * By a system catalog, we mean one that is created during the bootstrap + * phase of initdb. That includes not just the catalogs per se, but + * also their indexes, and TOAST tables and indexes if any. + * + * This function does not perform any catalog accesses. + * Some callers rely on that! + */ +bool +IsCatalogRelation(Relation relation) +{ + return IsCatalogRelationOid(RelationGetRelid(relation)); +} + +/* + * IsCatalogRelationOid + * True iff the relation identified by this OID is a system catalog. + * + * By a system catalog, we mean one that is created during the bootstrap + * phase of initdb. That includes not just the catalogs per se, but + * also their indexes, and TOAST tables and indexes if any. + * + * This function does not perform any catalog accesses. + * Some callers rely on that! + */ +bool +IsCatalogRelationOid(Oid relid) +{ + /* + * We consider a relation to be a system catalog if it has an OID that was + * manually assigned or assigned by genbki.pl. This includes all the + * defined catalogs, their indexes, and their TOAST tables and indexes. + * + * This rule excludes the relations in information_schema, which are not + * integral to the system and can be treated the same as user relations. + * (Since it's valid to drop and recreate information_schema, any rule + * that did not act this way would be wrong.) + * + * This test is reliable since an OID wraparound will skip this range of + * OIDs; see GetNewObjectId(). + */ + return (relid < (Oid) FirstBootstrapObjectId); +} + +/* + * IsToastRelation + * True iff relation is a TOAST support relation (or index). + * + * Does not perform any catalog accesses. + */ +bool +IsToastRelation(Relation relation) +{ + /* + * What we actually check is whether the relation belongs to a pg_toast + * namespace. This should be equivalent because of restrictions that are + * enforced elsewhere against creating user relations in, or moving + * relations into/out of, a pg_toast namespace. Notice also that this + * will not say "true" for toast tables belonging to other sessions' temp + * tables; we expect that other mechanisms will prevent access to those. + */ + 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); +} + +/* + * IsCatalogNamespace + * True iff namespace is pg_catalog. + * + * Does not perform any catalog accesses. + * + * NOTE: the reason this isn't a macro is to avoid having to include + * catalog/pg_namespace.h in a lot of places. + */ +bool +IsCatalogNamespace(Oid namespaceId) +{ + return namespaceId == PG_CATALOG_NAMESPACE; +} + +/* + * IsToastNamespace + * True iff namespace is pg_toast or my temporary-toast-table namespace. + * + * Does not perform any catalog accesses. + * + * Note: this will return false for temporary-toast-table namespaces belonging + * to other backends. Those are treated the same as other backends' regular + * temp table namespaces, and access is prevented where appropriate. + * If you need to check for those, you may be able to use isAnyTempNamespace, + * but beware that that does involve a catalog access. + */ +bool +IsToastNamespace(Oid namespaceId) +{ + return (namespaceId == PG_TOAST_NAMESPACE) || + isTempToastNamespace(namespaceId); +} + + +/* + * 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 was only true for + * schema and tablespace names. With 9.6, this is also true + * for roles. + */ +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. + * + * In older releases, this had to be hard-wired so that we could compute the + * locktag for a relation and lock it before examining its catalog entry. + * Since we now have MVCC catalog access, the race conditions that made that + * a hard requirement are gone, so we could look at relaxing this restriction. + * However, if we scanned the pg_class entry to find relisshared, and only + * then locked the relation, pg_class could get updated in the meantime, + * forcing us to scan the relation again, which would definitely be complex + * and might have undesirable performance consequences. 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 == SharedDescriptionRelationId || + relationId == SharedDependRelationId || + relationId == SharedSecLabelRelationId || + relationId == TableSpaceRelationId || + relationId == DbRoleSettingRelationId || + relationId == ReplicationOriginRelationId || + relationId == SubscriptionRelationId) + return true; + /* These are their indexes (see indexing.h) */ + if (relationId == AuthIdRolnameIndexId || + relationId == AuthIdOidIndexId || + relationId == AuthMemRoleMemIndexId || + relationId == AuthMemMemRoleIndexId || + relationId == DatabaseNameIndexId || + relationId == DatabaseOidIndexId || + relationId == SharedDescriptionObjIndexId || + relationId == SharedDependDependerIndexId || + relationId == SharedDependReferenceIndexId || + relationId == SharedSecLabelObjectIndexId || + relationId == TablespaceOidIndexId || + relationId == TablespaceNameIndexId || + relationId == DbRoleSettingDatidRolidIndexId || + relationId == ReplicationOriginIdentIndex || + relationId == ReplicationOriginNameIndex || + relationId == SubscriptionObjectIndexId || + relationId == SubscriptionNameIndexId) + return true; + /* These are their toast tables and toast indexes (see toasting.h) */ + if (relationId == PgAuthidToastTable || + relationId == PgAuthidToastIndex || + relationId == PgDatabaseToastTable || + relationId == PgDatabaseToastIndex || + relationId == PgDbRoleSettingToastTable || + relationId == PgDbRoleSettingToastIndex || + relationId == PgReplicationOriginToastTable || + relationId == PgReplicationOriginToastIndex || + relationId == PgShdescriptionToastTable || + relationId == PgShdescriptionToastIndex || + relationId == PgShseclabelToastTable || + relationId == PgShseclabelToastIndex || + relationId == PgSubscriptionToastTable || + relationId == PgSubscriptionToastIndex || + relationId == PgTablespaceToastTable || + relationId == PgTablespaceToastIndex) + return true; + return false; +} + + +/* + * GetNewOidWithIndex + * Generate a new OID that is unique within the system relation. + * + * 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 SnapshotAny in the test, so that + * we will see uncommitted rows. (We used to use SnapshotDirty, but that has + * the disadvantage that it ignores recently-deleted rows, creating a risk + * of transient conflicts for as long as our own MVCC snapshots think a + * recently-deleted row is live. The risk is far higher when selecting TOAST + * OIDs, because SnapshotToast considers dead rows as active indefinitely.) + * + * 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. This is a mostly reasonable assumption for + * system catalogs. + * + * Caller must have a suitable lock on the relation. + */ +Oid +GetNewOidWithIndex(Relation relation, Oid indexId, AttrNumber oidcolumn) +{ + Oid newOid; + SysScanDesc scan; + ScanKeyData key; + bool collides; + + /* Only system relations are supported */ + Assert(IsSystemRelation(relation)); + + /* In bootstrap mode, we don't have any indexes to use */ + if (IsBootstrapProcessingMode()) + return GetNewObjectId(); + + /* + * We should never be asked to generate a new pg_type OID during + * pg_upgrade; doing so would risk collisions with the OIDs it wants to + * assign. Hitting this assert means there's some path where we failed to + * ensure that a type OID is determined by commands in the dump script. + */ + Assert(!IsBinaryUpgrade || RelationGetRelid(relation) != TypeRelationId); + + /* Generate new OIDs until we find one not in the table */ + do + { + CHECK_FOR_INTERRUPTS(); + + newOid = GetNewObjectId(); + + ScanKeyInit(&key, + oidcolumn, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(newOid)); + + /* see notes above about using SnapshotAny */ + scan = systable_beginscan(relation, indexId, true, + SnapshotAny, 1, &key); + + collides = HeapTupleIsValid(systable_getnext(scan)); + + systable_endscan(scan); + } while (collides); + + return newOid; +} + +/* + * GetNewRelFileNode + * Generate a new relfilenode number that is unique within the + * database of 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 GetNewOidWithIndex(), 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, Relation pg_class, char relpersistence) +{ + RelFileNodeBackend rnode; + char *rpath; + bool collides; + BackendId backend; + + /* + * If we ever get here during pg_upgrade, there's something wrong; all + * relfilenode assignments during a binary-upgrade run should be + * determined by commands in the dump script. + */ + Assert(!IsBinaryUpgrade); + + switch (relpersistence) + { + case RELPERSISTENCE_TEMP: + backend = BackendIdForTempRelations(); + break; + case RELPERSISTENCE_UNLOGGED: + case RELPERSISTENCE_PERMANENT: + backend = InvalidBackendId; + break; + default: + elog(ERROR, "invalid relpersistence: %c", relpersistence); + return InvalidOid; /* placate compiler */ + } + + /* This logic should match RelationInitPhysicalAddr */ + rnode.node.spcNode = reltablespace ? reltablespace : MyDatabaseTableSpace; + rnode.node.dbNode = (rnode.node.spcNode == GLOBALTABLESPACE_OID) ? InvalidOid : MyDatabaseId; + + /* + * The relpath will vary based on the backend ID, so we must initialize + * that properly here to make sure that any collisions based on filename + * are properly detected. + */ + rnode.backend = backend; + + do + { + CHECK_FOR_INTERRUPTS(); + + /* Generate the OID */ + if (pg_class) + rnode.node.relNode = GetNewOidWithIndex(pg_class, ClassOidIndexId, + Anum_pg_class_oid); + else + rnode.node.relNode = GetNewObjectId(); + + /* Check for existing file of same name */ + rpath = relpath(rnode, MAIN_FORKNUM); + + if (access(rpath, F_OK) == 0) + { + /* definite collision */ + 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 + * 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.node.relNode; +} + +/* + * SQL callable interface for GetNewOidWithIndex(). Outside of initdb's + * direct insertions into catalog tables, and recovering from corruption, this + * should rarely be needed. + * + * Function is intentionally not documented in the user facing docs. + */ +Datum +pg_nextoid(PG_FUNCTION_ARGS) +{ + Oid reloid = PG_GETARG_OID(0); + Name attname = PG_GETARG_NAME(1); + Oid idxoid = PG_GETARG_OID(2); + Relation rel; + Relation idx; + HeapTuple atttuple; + Form_pg_attribute attform; + AttrNumber attno; + Oid newoid; + + /* + * As this function is not intended to be used during normal running, and + * only supports system catalogs (which require superuser permissions to + * modify), just checking for superuser ought to not obstruct valid + * usecases. + */ + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to call pg_nextoid()"))); + + rel = table_open(reloid, RowExclusiveLock); + idx = index_open(idxoid, RowExclusiveLock); + + if (!IsSystemRelation(rel)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("pg_nextoid() can only be used on system catalogs"))); + + if (idx->rd_index->indrelid != RelationGetRelid(rel)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("index \"%s\" does not belong to table \"%s\"", + RelationGetRelationName(idx), + RelationGetRelationName(rel)))); + + atttuple = SearchSysCacheAttName(reloid, NameStr(*attname)); + if (!HeapTupleIsValid(atttuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + NameStr(*attname), RelationGetRelationName(rel)))); + + attform = ((Form_pg_attribute) GETSTRUCT(atttuple)); + attno = attform->attnum; + + if (attform->atttypid != OIDOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("column \"%s\" is not of type oid", + NameStr(*attname)))); + + if (IndexRelationGetNumberOfKeyAttributes(idx) != 1 || + idx->rd_index->indkey.values[0] != attno) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("index \"%s\" is not the index for column \"%s\"", + RelationGetRelationName(idx), + NameStr(*attname)))); + + newoid = GetNewOidWithIndex(rel, idxoid, attno); + + ReleaseSysCache(atttuple); + table_close(rel, RowExclusiveLock); + index_close(idx, RowExclusiveLock); + + return newoid; +} |