/* * Guts of rule deletion. */ void RemoveRewriteRuleById(Oid ruleOid) { cqContext *pcqCtx; Relation event_relation; HeapTuple tuple; Oid eventRelationOid; bool hasMoreRules; /* * Find the tuple for the target rule. */ pcqCtx = caql_beginscan( NULL, cql("SELECT * FROM pg_rewrite " " WHERE oid = :1 " " FOR UPDATE ", ObjectIdGetDatum(ruleOid))); tuple = caql_getnext(pcqCtx); if (!HeapTupleIsValid(tuple)) elog(ERROR, "could not find tuple for rule %u", ruleOid); /* * We had better grab AccessExclusiveLock so that we know no other rule * additions/deletions are going on for this relation. Else we cannot set * relhasrules correctly. Besides, we don't want to be changing the * ruleset while queries are executing on the rel. */ eventRelationOid = ((Form_pg_rewrite) GETSTRUCT(tuple))->ev_class; event_relation = heap_open(eventRelationOid, AccessExclusiveLock); hasMoreRules = event_relation->rd_rules != NULL && event_relation->rd_rules->numLocks > 1; /* * Now delete the pg_rewrite tuple for the rule */ caql_delete_current(pcqCtx); caql_endscan(pcqCtx); /* * Set pg_class 'relhasrules' field correctly for event relation. * * Important side effect: an SI notice is broadcast to force all backends * (including me!) to update relcache entries with the new rule set. * Therefore, must do this even if relhasrules is still true! */ SetRelationRuleStatus(eventRelationOid, hasMoreRules, false); /* Close rel, but keep lock till commit... */ heap_close(event_relation, NoLock); }
/* * Drop a table space * * Be careful to check that the tablespace is empty. */ void RemoveTableSpace(List *names, DropBehavior behavior, bool missing_ok) { char *tablespacename; Relation rel; HeapTuple tuple; cqContext cqc; cqContext *pcqCtx; Oid tablespaceoid; int32 count; RelFileNode relfilenode; DbDirNode dbDirNode; PersistentFileSysState persistentState; ItemPointerData persistentTid; int64 persistentSerialNum; /* * General DROP (object) syntax allows fully qualified names, but * tablespaces are global objects that do not live in schemas, so * it is a syntax error if a fully qualified name was given. */ if (list_length(names) != 1) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("tablespace name may not be qualified"))); tablespacename = strVal(linitial(names)); /* Disallow CASCADE */ if (behavior == DROP_CASCADE) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("syntax at or near \"cascade\""))); /* * Find the target tuple */ rel = heap_open(TableSpaceRelationId, RowExclusiveLock); pcqCtx = caql_addrel(cqclr(&cqc), rel); tuple = caql_getfirst( pcqCtx, cql("SELECT * FROM pg_tablespace " " WHERE spcname = :1 " " FOR UPDATE ", CStringGetDatum(tablespacename))); if (!HeapTupleIsValid(tuple)) { /* No such tablespace, no need to hold the lock */ heap_close(rel, RowExclusiveLock); if (!missing_ok) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("tablespace \"%s\" does not exist", tablespacename))); } else { ereport(NOTICE, (errmsg("tablespace \"%s\" does not exist, skipping", tablespacename))); } return; } tablespaceoid = HeapTupleGetOid(tuple); /* Must be tablespace owner */ if (!pg_tablespace_ownercheck(tablespaceoid, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE, tablespacename); /* Disallow drop of the standard tablespaces, even by superuser */ if (tablespaceoid == GLOBALTABLESPACE_OID || tablespaceoid == DEFAULTTABLESPACE_OID) aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_TABLESPACE, tablespacename); /* * Check for any databases or relations defined in this tablespace, this * is logically the same as checkSharedDependencies, however we don't * actually track these in pg_shdepend, instead we lookup this information * in the gp_persistent_database/relation_node tables. */ /* ... */ /* * Remove the pg_tablespace tuple (this will roll back if we fail below) */ caql_delete_current(pcqCtx); /* * Remove any comments on this tablespace. */ DeleteSharedComments(tablespaceoid, TableSpaceRelationId); /* * Remove dependency on owner. * * If shared dependencies are added between filespace <=> tablespace * they will be deleted as well. */ deleteSharedDependencyRecordsFor(TableSpaceRelationId, tablespaceoid); /* MPP-6929: metadata tracking */ if (Gp_role == GP_ROLE_DISPATCH) MetaTrackDropObject(TableSpaceRelationId, tablespaceoid); /* * Acquire TablespaceCreateLock to ensure that no * MirroredFileSysObj_JustInTimeDbDirCreate is running concurrently. */ LWLockAcquire(TablespaceCreateLock, LW_EXCLUSIVE); /* * Check for any relations still defined in the tablespace. */ PersistentRelation_CheckTablespace(tablespaceoid, &count, &relfilenode); if (count > 0) { ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("tablespace \"%s\" is not empty", tablespacename))); } /* * Schedule the removal the physical infrastructure. * * Note: This only schedules the delete, the delete won't actually occur * until after the transaction has comitted. This should however do * everything it can to assure that the delete will occur sucessfully, * e.g. check permissions etc. */ /* * Schedule all persistent database directory removals for transaction commit. */ PersistentDatabase_DirIterateInit(); while (PersistentDatabase_DirIterateNext( &dbDirNode, &persistentState, &persistentTid, &persistentSerialNum)) { if (dbDirNode.tablespace != tablespaceoid) continue; /* * Database directory objects can linger in 'Drop Pending' state, etc, * when the mirror is down and needs drop work. So only pay attention * to 'Created' objects. */ if (persistentState != PersistentFileSysState_Created) continue; MirroredFileSysObj_ScheduleDropDbDir( &dbDirNode, &persistentTid, persistentSerialNum); } /* * Now schedule the tablespace directory removal. */ MirroredFileSysObj_ScheduleDropTablespaceDir(tablespaceoid); /* * Note: because we checked that the tablespace was empty, there should be * no need to worry about flushing shared buffers or free space map * entries for relations in the tablespace. * * CHECK THIS, also check if the lock makes any sense in this context. */ /* * Force synchronous commit, to minimize the window between removing the * files on-disk and marking the transaction committed. It's not great * that there is any window at all, but definitely we don't want to make * it larger than necessary. */ ForceSyncCommit(); /* * Allow MirroredFileSysObj_JustInTimeDbDirCreate again. */ LWLockRelease(TablespaceCreateLock); /* We keep the lock on the row in pg_tablespace until commit */ heap_close(rel, NoLock); /* Note: no need for dispatch, that is handled in utility.c */ return; }
/* * shdepChangeDep * * Update shared dependency records to account for an updated referenced * object. This is an internal workhorse for operations such as changing * an object's owner. * * There must be no more than one existing entry for the given dependent * object and dependency type! So in practice this can only be used for * updating SHARED_DEPENDENCY_OWNER entries, which should have that property. * * If there is no previous entry, we assume it was referencing a PINned * object, so we create a new entry. If the new referenced object is * PINned, we don't create an entry (and drop the old one, if any). * * sdepRel must be the pg_shdepend relation, already opened and suitably * locked. */ static void shdepChangeDep(Relation sdepRel, Oid classid, Oid objid, Oid refclassid, Oid refobjid, SharedDependencyType deptype) { Oid dbid = classIdGetDbId(classid); bool bGotOne = false; HeapTuple oldtup = NULL; HeapTuple scantup; cqContext *pcqCtx; cqContext cqc; /* * Make sure the new referenced object doesn't go away while we record the * dependency. */ shdepLockAndCheckObject(refclassid, refobjid); /* * Look for a previous entry */ pcqCtx = caql_beginscan( caql_addrel(cqclr(&cqc), sdepRel), cql("SELECT * FROM pg_shdepend " " WHERE dbid = :1 " " AND classid = :2 " " AND objid = :3 " " FOR UPDATE ", ObjectIdGetDatum(dbid), ObjectIdGetDatum(classid), ObjectIdGetDatum(objid))); while (HeapTupleIsValid(scantup = caql_getnext(pcqCtx))) { /* Ignore if not of the target dependency type */ if (((Form_pg_shdepend) GETSTRUCT(scantup))->deptype != deptype) continue; /* Caller screwed up if multiple matches */ if (bGotOne) elog(ERROR, "multiple pg_shdepend entries for object %u/%u deptype %c", classid, objid, deptype); bGotOne = true; } caql_endscan(pcqCtx); /* XXX XXX XXX XXX XXX XXX XXX XXX XXX * Should match this logic: * * if isSharedObjectpinned * if Gotone then drop it * else * if Gotone * then update it * else insert it * * XXX XXX XXX XXX XXX XXX XXX XXX XXX */ if (!bGotOne) /* no match */ { /* if no match and pinned, new entry not needed */ if (isSharedObjectPinned(refclassid, refobjid, sdepRel)) { /* just return -- don't need to free anything because * sdelRel was passed in, and pcqCtx is freed */ return; } pcqCtx = caql_beginscan( caql_addrel(cqclr(&cqc), sdepRel), cql("INSERT INTO pg_shdepend ", NULL)); /* Need to insert new entry */ Datum values[Natts_pg_shdepend]; bool nulls[Natts_pg_shdepend]; memset(nulls, 0, sizeof(nulls)); values[Anum_pg_shdepend_dbid - 1] = ObjectIdGetDatum(dbid); values[Anum_pg_shdepend_classid - 1] = ObjectIdGetDatum(classid); values[Anum_pg_shdepend_objid - 1] = ObjectIdGetDatum(objid); values[Anum_pg_shdepend_refclassid - 1] = ObjectIdGetDatum(refclassid); values[Anum_pg_shdepend_refobjid - 1] = ObjectIdGetDatum(refobjid); values[Anum_pg_shdepend_deptype - 1] = CharGetDatum(deptype); /* * we are reusing oldtup just to avoid declaring a new variable, but * it's certainly a new tuple */ oldtup = caql_form_tuple(pcqCtx, values, nulls); caql_insert(pcqCtx, oldtup); /* and Update indexes (implicit) */ heap_freetuple(oldtup); caql_endscan(pcqCtx); } else { /* XXX XXX Do the scan again, but do the update/delete this time */ pcqCtx = caql_beginscan( caql_addrel(cqclr(&cqc), sdepRel), cql("SELECT * FROM pg_shdepend " " WHERE dbid = :1 " " AND classid = :2 " " AND objid = :3 " " FOR UPDATE ", ObjectIdGetDatum(dbid), ObjectIdGetDatum(classid), ObjectIdGetDatum(objid))); while (HeapTupleIsValid(scantup = caql_getnext(pcqCtx))) { /* Ignore if not of the target dependency type */ if (((Form_pg_shdepend) GETSTRUCT(scantup))->deptype != deptype) continue; /* * NOTE: already tested for multiple matches - just use * first one */ if (isSharedObjectPinned(refclassid, refobjid, sdepRel)) { /* No new entry needed, so just delete existing entry if any */ caql_delete_current(pcqCtx); } else { oldtup = heap_copytuple(scantup); /* Need to update existing entry */ Form_pg_shdepend shForm = (Form_pg_shdepend) GETSTRUCT(oldtup); /* Since oldtup is a copy, we can just modify it in-memory */ shForm->refclassid = refclassid; shForm->refobjid = refobjid; caql_update_current(pcqCtx, oldtup); /* and Update indexes (implicit) */ heap_freetuple(oldtup); } break; } caql_endscan(pcqCtx); } }