/* * IsCatalogClass * True iff the relation is a system catalog relation. * * Check IsCatalogRelation() for details. */ bool IsCatalogClass(Oid relid, Form_pg_class reltuple) { Oid relnamespace = reltuple->relnamespace; /* * Never consider relations outside pg_catalog/pg_toast to be catalog * relations. */ if (!IsSystemNamespace(relnamespace) && !IsToastNamespace(relnamespace)) return false; /* ---- * Check whether the oid was assigned during initdb, when creating the * initial template database. Minus the relations in information_schema * excluded above, these are integral part of the system. * We could instead check whether the relation is pinned in pg_depend, but * this is noticeably cheaper and doesn't require catalog access. * * This test is safe since even an oid wraparound will preserve this * property (c.f. GetNewObjectId()) and it has the advantage that it works * correctly even if a user decides to create a relation in the pg_catalog * namespace. * ---- */ return relid < FirstNormalObjectId; }
/* * 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)) || IsAoSegmentNamespace(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); }
/* * 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)) { Oid result; /* * 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)); result= GetNewObjectId(); if (IsSystemNamespace(RelationGetNamespace(relation))) { if (Gp_role == GP_ROLE_EXECUTE) { elog(DEBUG1,"Allocating Oid %u on relid %u %s in EXECUTE mode",result,relation->rd_id,RelationGetRelationName(relation)); } if (Gp_role == GP_ROLE_DISPATCH) { elog(DEBUG5,"Allocating Oid %u on relid %u %s in DISPATCH mode",result,relation->rd_id,RelationGetRelationName(relation)); } } return result; } /* Otherwise, use the index to find a nonconflicting OID */ indexrel = index_open(oidIndex, AccessShareLock); newOid = GetNewOidWithIndex(relation, indexrel); index_close(indexrel, AccessShareLock); return newOid; }
/** * Method determines if a relation is master-only or distributed among segments. * Input: * relationOid * Output: * true if masteronly */ bool isMasterOnly(Oid relationOid) { Assert(relationOid != InvalidOid); Oid schemaOid = get_rel_namespace(relationOid); GpPolicy *distributionPolicy = GpPolicyFetch(CurrentMemoryContext, relationOid); bool masterOnly = (Gp_role == GP_ROLE_UTILITY || IsSystemNamespace(schemaOid) || IsToastNamespace(schemaOid) || IsAoSegmentNamespace(schemaOid) || (distributionPolicy == NULL) || (distributionPolicy->ptype == POLICYTYPE_ENTRY)); return masterOnly; }
/* * 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 { CHECK_FOR_INTERRUPTS(); 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); if (IsSystemNamespace(RelationGetNamespace(relation))) { if (Gp_role == GP_ROLE_EXECUTE) { if (relation->rd_id != 2604 /* pg_attrdef */ && relation->rd_id != 2606 /* pg_constraint */ && relation->rd_id != 2615 /* pg_namespace */) elog(DEBUG1,"Allocating Oid %u with index on relid %u %s in EXECUTE mode",newOid,relation->rd_id, RelationGetRelationName(relation)); else elog(DEBUG4,"Allocating Oid %u with index on relid %u %s in EXECUTE mode",newOid,relation->rd_id, RelationGetRelationName(relation)); } if (Gp_role == GP_ROLE_DISPATCH) { elog(DEBUG5,"Allocating Oid %u with index on relid %u %s in DISPATCH mode",newOid,relation->rd_id, RelationGetRelationName(relation)); } } return newOid; }
/* * OIDs for catalog object are normally allocated in the master, and * executor nodes should just use the OIDs passed by the master. But * there are some exceptions. */ static bool RelationNeedsSynchronizedOIDs(Relation relation) { if (IsSystemNamespace(RelationGetNamespace(relation))) { switch(RelationGetRelid(relation)) { /* * pg_largeobject is more like a user table, and has * different contents in each segment and master. */ case LargeObjectRelationId: return false; /* * We don't currently synchronize the OIDs of these catalogs. * It's a bit sketchy that we don't, but we get away with it * because these OIDs don't appear in any of the Node structs * that are dispatched from master to segments. (Except for the * OIDs, the contents of these tables should be in sync.) */ case RewriteRelationId: case TriggerRelationId: case AccessMethodOperatorRelationId: case AccessMethodProcedureRelationId: return false; } /* * All other system catalogs are assumed to need synchronized * OIDs. */ return true; } return false; }
/* * check_relation_privileges * * It actually checks required permissions on a certain relation * and its columns. */ static bool check_relation_privileges(Oid relOid, Bitmapset *selected, Bitmapset *inserted, Bitmapset *updated, uint32 required, bool abort_on_violation) { ObjectAddress object; char *audit_name; Bitmapset *columns; int index; char relkind = get_rel_relkind(relOid); bool result = true; /* * Hardwired Policies: SE-PostgreSQL enforces - clients cannot modify * system catalogs using DMLs - clients cannot reference/modify toast * relations using DMLs */ if (sepgsql_getenforce() > 0) { Oid relnamespace = get_rel_namespace(relOid); if (IsSystemNamespace(relnamespace) && (required & (SEPG_DB_TABLE__UPDATE | SEPG_DB_TABLE__INSERT | SEPG_DB_TABLE__DELETE)) != 0) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("SELinux: hardwired security policy violation"))); if (relkind == RELKIND_TOASTVALUE) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("SELinux: hardwired security policy violation"))); } /* * Check permissions on the relation */ object.classId = RelationRelationId; object.objectId = relOid; object.objectSubId = 0; audit_name = getObjectIdentity(&object); switch (relkind) { case RELKIND_RELATION: case RELKIND_PARTITIONED_TABLE: result = sepgsql_avc_check_perms(&object, SEPG_CLASS_DB_TABLE, required, audit_name, abort_on_violation); break; case RELKIND_SEQUENCE: Assert((required & ~SEPG_DB_TABLE__SELECT) == 0); if (required & SEPG_DB_TABLE__SELECT) result = sepgsql_avc_check_perms(&object, SEPG_CLASS_DB_SEQUENCE, SEPG_DB_SEQUENCE__GET_VALUE, audit_name, abort_on_violation); break; case RELKIND_VIEW: result = sepgsql_avc_check_perms(&object, SEPG_CLASS_DB_VIEW, SEPG_DB_VIEW__EXPAND, audit_name, abort_on_violation); break; default: /* nothing to be checked */ break; } pfree(audit_name); /* * Only columns owned by relations shall be checked */ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE) return true; /* * Check permissions on the columns */ selected = fixup_whole_row_references(relOid, selected); inserted = fixup_whole_row_references(relOid, inserted); updated = fixup_whole_row_references(relOid, updated); columns = bms_union(selected, bms_union(inserted, updated)); while ((index = bms_first_member(columns)) >= 0) { AttrNumber attnum; uint32 column_perms = 0; if (bms_is_member(index, selected)) column_perms |= SEPG_DB_COLUMN__SELECT; if (bms_is_member(index, inserted)) { if (required & SEPG_DB_TABLE__INSERT) column_perms |= SEPG_DB_COLUMN__INSERT; } if (bms_is_member(index, updated)) { if (required & SEPG_DB_TABLE__UPDATE) column_perms |= SEPG_DB_COLUMN__UPDATE; } if (column_perms == 0) continue; /* obtain column's permission */ attnum = index + FirstLowInvalidHeapAttributeNumber; object.classId = RelationRelationId; object.objectId = relOid; object.objectSubId = attnum; audit_name = getObjectDescription(&object); result = sepgsql_avc_check_perms(&object, SEPG_CLASS_DB_COLUMN, column_perms, audit_name, abort_on_violation); pfree(audit_name); if (!result) return result; } return true; }
/* * Alter table space move * * Allows a user to move all of their objects in a given tablespace in the * current database to another tablespace. Only objects which the user is * considered to be an owner of are moved and the user must have CREATE rights * on the new tablespace. These checks should mean that ALTER TABLE will never * fail due to permissions, but note that permissions will also be checked at * that level. Objects can be ALL, TABLES, INDEXES, or MATERIALIZED VIEWS. * * All to-be-moved objects are locked first. If NOWAIT is specified and the * lock can't be acquired then we ereport(ERROR). */ Oid AlterTableSpaceMove(AlterTableSpaceMoveStmt *stmt) { List *relations = NIL; ListCell *l; ScanKeyData key[1]; Relation rel; HeapScanDesc scan; HeapTuple tuple; Oid orig_tablespaceoid; Oid new_tablespaceoid; List *role_oids = roleNamesToIds(stmt->roles); /* Ensure we were not asked to move something we can't */ if (!stmt->move_all && stmt->objtype != OBJECT_TABLE && stmt->objtype != OBJECT_INDEX && stmt->objtype != OBJECT_MATVIEW) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("only tables, indexes, and materialized views exist in tablespaces"))); /* Get the orig and new tablespace OIDs */ orig_tablespaceoid = get_tablespace_oid(stmt->orig_tablespacename, false); new_tablespaceoid = get_tablespace_oid(stmt->new_tablespacename, false); /* Can't move shared relations in to or out of pg_global */ /* This is also checked by ATExecSetTableSpace, but nice to stop earlier */ if (orig_tablespaceoid == GLOBALTABLESPACE_OID || new_tablespaceoid == GLOBALTABLESPACE_OID) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot move relations in to or out of pg_global tablespace"))); /* * Must have CREATE rights on the new tablespace, unless it is the * database default tablespace (which all users implicitly have CREATE * rights on). */ if (OidIsValid(new_tablespaceoid) && new_tablespaceoid != MyDatabaseTableSpace) { AclResult aclresult; aclresult = pg_tablespace_aclcheck(new_tablespaceoid, GetUserId(), ACL_CREATE); if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_TABLESPACE, get_tablespace_name(new_tablespaceoid)); } /* * Now that the checks are done, check if we should set either to * InvalidOid because it is our database's default tablespace. */ if (orig_tablespaceoid == MyDatabaseTableSpace) orig_tablespaceoid = InvalidOid; if (new_tablespaceoid == MyDatabaseTableSpace) new_tablespaceoid = InvalidOid; /* no-op */ if (orig_tablespaceoid == new_tablespaceoid) return new_tablespaceoid; /* * Walk the list of objects in the tablespace and move them. This will * only find objects in our database, of course. */ ScanKeyInit(&key[0], Anum_pg_class_reltablespace, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(orig_tablespaceoid)); rel = heap_open(RelationRelationId, AccessShareLock); scan = heap_beginscan_catalog(rel, 1, key); while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) { Oid relOid = HeapTupleGetOid(tuple); Form_pg_class relForm; relForm = (Form_pg_class) GETSTRUCT(tuple); /* * Do not move objects in pg_catalog as part of this, if an admin * really wishes to do so, they can issue the individual ALTER * commands directly. * * Also, explicitly avoid any shared tables, temp tables, or TOAST * (TOAST will be moved with the main table). */ if (IsSystemNamespace(relForm->relnamespace) || relForm->relisshared || isAnyTempNamespace(relForm->relnamespace) || relForm->relnamespace == PG_TOAST_NAMESPACE) continue; /* Only consider objects which live in tablespaces */ if (relForm->relkind != RELKIND_RELATION && relForm->relkind != RELKIND_INDEX && relForm->relkind != RELKIND_MATVIEW) continue; /* Check if we were asked to only move a certain type of object */ if (!stmt->move_all && ((stmt->objtype == OBJECT_TABLE && relForm->relkind != RELKIND_RELATION) || (stmt->objtype == OBJECT_INDEX && relForm->relkind != RELKIND_INDEX) || (stmt->objtype == OBJECT_MATVIEW && relForm->relkind != RELKIND_MATVIEW))) continue; /* Check if we are only moving objects owned by certain roles */ if (role_oids != NIL && !list_member_oid(role_oids, relForm->relowner)) continue; /* * Handle permissions-checking here since we are locking the tables * and also to avoid doing a bunch of work only to fail part-way. * Note that permissions will also be checked by AlterTableInternal(). * * Caller must be considered an owner on the table to move it. */ if (!pg_class_ownercheck(relOid, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, NameStr(relForm->relname)); if (stmt->nowait && !ConditionalLockRelationOid(relOid, AccessExclusiveLock)) ereport(ERROR, (errcode(ERRCODE_OBJECT_IN_USE), errmsg("aborting due to \"%s\".\"%s\" --- lock not available", get_namespace_name(relForm->relnamespace), NameStr(relForm->relname)))); else LockRelationOid(relOid, AccessExclusiveLock); /* Add to our list of objects to move */ relations = lappend_oid(relations, relOid); } heap_endscan(scan); heap_close(rel, AccessShareLock); if (relations == NIL) ereport(NOTICE, (errcode(ERRCODE_NO_DATA_FOUND), errmsg("no matching relations in tablespace \"%s\" found", orig_tablespaceoid == InvalidOid ? "(database default)" : get_tablespace_name(orig_tablespaceoid)))); /* Everything is locked, loop through and move all of the relations. */ foreach(l, relations) { List *cmds = NIL; AlterTableCmd *cmd = makeNode(AlterTableCmd); cmd->subtype = AT_SetTableSpace; cmd->name = stmt->new_tablespacename; cmds = lappend(cmds, cmd); AlterTableInternal(lfirst_oid(l), cmds, false); }