/* * Find the searchslot tuple and delete it, and execute any constraints * and per-row triggers. * * Caller is responsible for opening the indexes. */ void ExecSimpleRelationDelete(EState *estate, EPQState *epqstate, TupleTableSlot *searchslot) { bool skip_tuple = false; ResultRelInfo *resultRelInfo = estate->es_result_relation_info; Relation rel = resultRelInfo->ri_RelationDesc; /* For now we support only tables. */ Assert(rel->rd_rel->relkind == RELKIND_RELATION); CheckCmdReplicaIdentity(rel, CMD_DELETE); /* BEFORE ROW DELETE Triggers */ if (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_delete_before_row) { skip_tuple = !ExecBRDeleteTriggers(estate, epqstate, resultRelInfo, &searchslot->tts_tuple->t_self, NULL); } if (!skip_tuple) { List *recheckIndexes = NIL; /* OK, delete the tuple */ simple_heap_delete(rel, &searchslot->tts_tuple->t_self); /* AFTER ROW DELETE Triggers */ ExecARDeleteTriggers(estate, resultRelInfo, &searchslot->tts_tuple->t_self, NULL, NULL); list_free(recheckIndexes); } }
/* * 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, int32 objsubid, Oid refclassid, Oid refobjid, SharedDependencyType deptype) { Oid dbid = classIdGetDbId(classid); HeapTuple oldtup = NULL; HeapTuple scantup; ScanKeyData key[4]; SysScanDesc scan; /* * Make sure the new referenced object doesn't go away while we record the * dependency. */ shdepLockAndCheckObject(refclassid, refobjid); /* * Look for a previous entry */ ScanKeyInit(&key[0], Anum_pg_shdepend_dbid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(dbid)); ScanKeyInit(&key[1], Anum_pg_shdepend_classid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(classid)); ScanKeyInit(&key[2], Anum_pg_shdepend_objid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(objid)); ScanKeyInit(&key[3], Anum_pg_shdepend_objsubid, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(objsubid)); scan = systable_beginscan(sdepRel, SharedDependDependerIndexId, true, SnapshotNow, 4, key); while ((scantup = systable_getnext(scan)) != NULL) { /* Ignore if not of the target dependency type */ if (((Form_pg_shdepend) GETSTRUCT(scantup))->deptype != deptype) continue; /* Caller screwed up if multiple matches */ if (oldtup) elog(ERROR, "multiple pg_shdepend entries for object %u/%u/%d deptype %c", classid, objid, objsubid, deptype); oldtup = heap_copytuple(scantup); } systable_endscan(scan); if (isSharedObjectPinned(refclassid, refobjid, sdepRel)) { /* No new entry needed, so just delete existing entry if any */ if (oldtup) simple_heap_delete(sdepRel, &oldtup->t_self); } else if (oldtup) { /* 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; simple_heap_update(sdepRel, &oldtup->t_self, oldtup); /* keep indexes current */ CatalogUpdateIndexes(sdepRel, oldtup); } else { /* Need to insert new entry */ Datum values[Natts_pg_shdepend]; bool nulls[Natts_pg_shdepend]; memset(nulls, false, 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_objsubid - 1] = Int32GetDatum(objsubid); 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 = heap_form_tuple(RelationGetDescr(sdepRel), values, nulls); simple_heap_insert(sdepRel, oldtup); /* keep indexes current */ CatalogUpdateIndexes(sdepRel, oldtup); } if (oldtup) heap_freetuple(oldtup); }
/* * CreateSharedComments -- * * Create a comment for the specified shared object descriptor. Inserts a * new pg_shdescription tuple, or replaces an existing one with the same key. * * If the comment given is null or an empty string, instead delete any * existing comment for the specified key. */ void CreateSharedComments(Oid oid, Oid classoid, char *comment) { Relation shdescription; ScanKeyData skey[2]; SysScanDesc sd; HeapTuple oldtuple; HeapTuple newtuple = NULL; Datum values[Natts_pg_shdescription]; bool nulls[Natts_pg_shdescription]; bool replaces[Natts_pg_shdescription]; int i; /* Reduce empty-string to NULL case */ if (comment != NULL && strlen(comment) == 0) comment = NULL; /* Prepare to form or update a tuple, if necessary */ if (comment != NULL) { for (i = 0; i < Natts_pg_shdescription; i++) { nulls[i] = false; replaces[i] = true; } values[Anum_pg_shdescription_objoid - 1] = ObjectIdGetDatum(oid); values[Anum_pg_shdescription_classoid - 1] = ObjectIdGetDatum(classoid); values[Anum_pg_shdescription_description - 1] = CStringGetTextDatum(comment); } /* Use the index to search for a matching old tuple */ ScanKeyInit(&skey[0], Anum_pg_shdescription_objoid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(oid)); ScanKeyInit(&skey[1], Anum_pg_shdescription_classoid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(classoid)); shdescription = heap_open(SharedDescriptionRelationId, RowExclusiveLock); sd = systable_beginscan(shdescription, SharedDescriptionObjIndexId, true, SnapshotNow, 2, skey); while ((oldtuple = systable_getnext(sd)) != NULL) { /* Found the old tuple, so delete or update it */ if (comment == NULL) simple_heap_delete(shdescription, &oldtuple->t_self); else { newtuple = heap_modify_tuple(oldtuple, RelationGetDescr(shdescription), values, nulls, replaces); simple_heap_update(shdescription, &oldtuple->t_self, newtuple); } break; /* Assume there can be only one match */ } systable_endscan(sd); /* If we didn't find an old tuple, insert a new one */ if (newtuple == NULL && comment != NULL) { newtuple = heap_form_tuple(RelationGetDescr(shdescription), values, nulls); simple_heap_insert(shdescription, newtuple); } /* Update indexes, if necessary */ if (newtuple != NULL) { CatalogUpdateIndexes(shdescription, newtuple); heap_freetuple(newtuple); } /* Done */ heap_close(shdescription, NoLock); }
/* ---------------------------------------------------------------- * caql_getcount() * Perform COUNT(*) or DELETE * ---------------------------------------------------------------- */ int caql_getcount(cqContext *pCtx0, cq_list *pcql) { const char* caql_str = pcql->caqlStr; const char* filenam = pcql->filename; int lineno = pcql->lineno; struct caql_hash_cookie *pchn = cq_lookup(caql_str, strlen(caql_str), pcql); cqContext *pCtx; cqContext cqc; HeapTuple tuple; Relation rel; int ii = 0; if (NULL == pchn) elog(ERROR, "invalid caql string: %s\nfile: %s, line %d", caql_str, filenam, lineno); Assert(!pchn->bInsert); /* INSERT not allowed */ /* use the provided context, or provide a clean local ctx */ if (pCtx0) pCtx = pCtx0; else pCtx = cqclr(&cqc); pCtx = caql_switch(pchn, pCtx, pcql); /* NOTE: caql_switch frees the pcql */ rel = pCtx->cq_heap_rel; /* use the SysCache */ if (pCtx->cq_usesyscache) { tuple = SearchSysCacheKeyArray(pCtx->cq_cacheId, pCtx->cq_NumKeys, pCtx->cq_cacheKeys); if (HeapTupleIsValid(tuple)) { ii++; pCtx->cq_lasttup = tuple; if (pchn->bDelete) simple_heap_delete(rel, &tuple->t_self); ReleaseSysCache(tuple); /* only one */ } caql_heapclose(pCtx); return (ii); } while (HeapTupleIsValid(tuple = systable_getnext(pCtx->cq_sysScan))) { if (HeapTupleIsValid(tuple) && pchn->bDelete) { pCtx->cq_lasttup = tuple; if (pchn->bDelete) simple_heap_delete(rel, &tuple->t_self); } ii++; } systable_endscan(pCtx->cq_sysScan); caql_heapclose(pCtx); return (ii); }
/* * Delete a single constraint record. */ void RemoveConstraintById(Oid conId) { Relation conDesc; HeapTuple tup; Form_pg_constraint con; conDesc = heap_open(ConstraintRelationId, RowExclusiveLock); tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(conId)); if (!HeapTupleIsValid(tup)) /* should not happen */ elog(ERROR, "cache lookup failed for constraint %u", conId); con = (Form_pg_constraint) GETSTRUCT(tup); /* * Special processing depending on what the constraint is for. */ if (OidIsValid(con->conrelid)) { Relation rel; /* * If the constraint is for a relation, open and exclusive-lock the * relation it's for. */ rel = heap_open(con->conrelid, AccessExclusiveLock); /* * We need to update the relcheck count if it is a check constraint * being dropped. This update will force backends to rebuild relcache * entries when we commit. */ if (con->contype == CONSTRAINT_CHECK) { Relation pgrel; HeapTuple relTup; Form_pg_class classForm; pgrel = heap_open(RelationRelationId, RowExclusiveLock); relTup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(con->conrelid)); if (!HeapTupleIsValid(relTup)) elog(ERROR, "cache lookup failed for relation %u", con->conrelid); classForm = (Form_pg_class) GETSTRUCT(relTup); if (classForm->relchecks == 0) /* should not happen */ elog(ERROR, "relation \"%s\" has relchecks = 0", RelationGetRelationName(rel)); classForm->relchecks--; simple_heap_update(pgrel, &relTup->t_self, relTup); CatalogUpdateIndexes(pgrel, relTup); heap_freetuple(relTup); heap_close(pgrel, RowExclusiveLock); } /* Keep lock on constraint's rel until end of xact */ heap_close(rel, NoLock); } else if (OidIsValid(con->contypid)) { /* * XXX for now, do nothing special when dropping a domain constraint * * Probably there should be some form of locking on the domain type, * but we have no such concept at the moment. */ } else elog(ERROR, "constraint %u is not of a known type", conId); /* Fry the constraint itself */ simple_heap_delete(conDesc, &tup->t_self); /* Clean up */ ReleaseSysCache(tup); heap_close(conDesc, RowExclusiveLock); }
/* * Drop a table space * * Be careful to check that the tablespace is empty. */ void DropTableSpace(DropTableSpaceStmt *stmt) { #ifdef HAVE_SYMLINK char *tablespacename = stmt->tablespacename; HeapScanDesc scandesc; Relation rel; HeapTuple tuple; ScanKeyData entry[1]; Oid tablespaceoid; /* * Find the target tuple */ rel = heap_open(TableSpaceRelationId, RowExclusiveLock); ScanKeyInit(&entry[0], Anum_pg_tablespace_spcname, BTEqualStrategyNumber, F_NAMEEQ, CStringGetDatum(tablespacename)); scandesc = heap_beginscan_catalog(rel, 1, entry); tuple = heap_getnext(scandesc, ForwardScanDirection); if (!HeapTupleIsValid(tuple)) { if (!stmt->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))); /* XXX I assume I need one or both of these next two calls */ heap_endscan(scandesc); heap_close(rel, NoLock); } 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); /* DROP hook for the tablespace being removed */ InvokeObjectDropHook(TableSpaceRelationId, tablespaceoid, 0); /* * Remove the pg_tablespace tuple (this will roll back if we fail below) */ simple_heap_delete(rel, &tuple->t_self); heap_endscan(scandesc); /* * Remove any comments or security labels on this tablespace. */ DeleteSharedComments(tablespaceoid, TableSpaceRelationId); DeleteSharedSecurityLabel(tablespaceoid, TableSpaceRelationId); /* * Remove dependency on owner. */ deleteSharedDependencyRecordsFor(TableSpaceRelationId, tablespaceoid, 0); /* * Acquire TablespaceCreateLock to ensure that no TablespaceCreateDbspace * is running concurrently. */ LWLockAcquire(TablespaceCreateLock, LW_EXCLUSIVE); /* * Try to remove the physical infrastructure. */ if (!destroy_tablespace_directories(tablespaceoid, false)) { /* * Not all files deleted? However, there can be lingering empty files * in the directories, left behind by for example DROP TABLE, that * have been scheduled for deletion at next checkpoint (see comments * in mdunlink() for details). We could just delete them immediately, * but we can't tell them apart from important data files that we * mustn't delete. So instead, we force a checkpoint which will clean * out any lingering files, and try again. * * XXX On Windows, an unlinked file persists in the directory listing * until no process retains an open handle for the file. The DDL * commands that schedule files for unlink send invalidation messages * directing other PostgreSQL processes to close the files. DROP * TABLESPACE should not give up on the tablespace becoming empty * until all relevant invalidation processing is complete. */ RequestCheckpoint(CHECKPOINT_IMMEDIATE | CHECKPOINT_FORCE | CHECKPOINT_WAIT); if (!destroy_tablespace_directories(tablespaceoid, false)) { /* Still not empty, the files must be important then */ ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("tablespace \"%s\" is not empty", tablespacename))); } } /* Record the filesystem change in XLOG */ { xl_tblspc_drop_rec xlrec; xlrec.ts_id = tablespaceoid; XLogBeginInsert(); XLogRegisterData((char *) &xlrec, sizeof(xl_tblspc_drop_rec)); (void) XLogInsert(RM_TBLSPC_ID, XLOG_TBLSPC_DROP); } /* * 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. */ /* * 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 TablespaceCreateDbspace again. */ LWLockRelease(TablespaceCreateLock); /* We keep the lock on pg_tablespace until commit */ heap_close(rel, NoLock); #else /* !HAVE_SYMLINK */ ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("tablespaces are not supported on this platform"))); #endif /* HAVE_SYMLINK */ }
/* *-------------------------------------------------------------- * AtCommit_Notify * * This is called at transaction commit. * * If there are outbound notify requests in the pendingNotifies list, * scan pg_listener for matching tuples, and either signal the other * backend or send a message to our own frontend. * * NOTE: we are still inside the current transaction, therefore can * piggyback on its committing of changes. * * Results: * XXX * * Side effects: * Tuples in pg_listener that have matching relnames and other peoples' * listenerPIDs are updated with a nonzero notification field. * *-------------------------------------------------------------- */ void AtCommit_Notify(void) { Relation lRel; TupleDesc tdesc; HeapScanDesc scan; HeapTuple lTuple, rTuple; Datum value[Natts_pg_listener]; char repl[Natts_pg_listener], nulls[Natts_pg_listener]; if (pendingNotifies == NIL) return; /* no NOTIFY statements in this transaction */ /* * NOTIFY is disabled if not normal processing mode. This test used to be * in xact.c, but it seems cleaner to do it here. */ if (!IsNormalProcessingMode()) { ClearPendingNotifies(); return; } if (Trace_notify) elog(DEBUG1, "AtCommit_Notify"); /* preset data to update notify column to MyProcPid */ nulls[0] = nulls[1] = nulls[2] = ' '; repl[0] = repl[1] = repl[2] = ' '; repl[Anum_pg_listener_notify - 1] = 'r'; value[0] = value[1] = value[2] = (Datum) 0; value[Anum_pg_listener_notify - 1] = Int32GetDatum(MyProcPid); lRel = heap_open(ListenerRelationId, ExclusiveLock); tdesc = RelationGetDescr(lRel); scan = heap_beginscan(lRel, SnapshotNow, 0, NULL); while ((lTuple = heap_getnext(scan, ForwardScanDirection)) != NULL) { Form_pg_listener listener = (Form_pg_listener) GETSTRUCT(lTuple); char *relname = NameStr(listener->relname); int32 listenerPID = listener->listenerpid; if (!AsyncExistsPendingNotify(relname)) continue; if (listenerPID == MyProcPid) { /* * Self-notify: no need to bother with table update. Indeed, we * *must not* clear the notification field in this path, or we * could lose an outside notify, which'd be bad for applications * that ignore self-notify messages. */ if (Trace_notify) elog(DEBUG1, "AtCommit_Notify: notifying self"); NotifyMyFrontEnd(relname, listenerPID); } else { if (Trace_notify) elog(DEBUG1, "AtCommit_Notify: notifying pid %d", listenerPID); /* * If someone has already notified this listener, we don't bother * modifying the table, but we do still send a SIGUSR2 signal, * just in case that backend missed the earlier signal for some * reason. It's OK to send the signal first, because the other * guy can't read pg_listener until we unlock it. */ if (kill(listenerPID, SIGUSR2) < 0) { /* * Get rid of pg_listener entry if it refers to a PID that no * longer exists. Presumably, that backend crashed without * deleting its pg_listener entries. This code used to only * delete the entry if errno==ESRCH, but as far as I can see * we should just do it for any failure (certainly at least * for EPERM too...) */ simple_heap_delete(lRel, &lTuple->t_self); } else if (listener->notification == 0) { HTSU_Result result; ItemPointerData update_ctid; TransactionId update_xmax; rTuple = heap_modifytuple(lTuple, tdesc, value, nulls, repl); /* * We cannot use simple_heap_update here because the tuple * could have been modified by an uncommitted transaction; * specifically, since UNLISTEN releases exclusive lock on the * table before commit, the other guy could already have tried * to unlisten. There are no other cases where we should be * able to see an uncommitted update or delete. Therefore, our * response to a HeapTupleBeingUpdated result is just to * ignore it. We do *not* wait for the other guy to commit * --- that would risk deadlock, and we don't want to block * while holding the table lock anyway for performance * reasons. We also ignore HeapTupleUpdated, which could occur * if the other guy commits between our heap_getnext and * heap_update calls. */ result = heap_update(lRel, &lTuple->t_self, rTuple, &update_ctid, &update_xmax, GetCurrentCommandId(), InvalidSnapshot, false /* no wait for commit */ ); switch (result) { case HeapTupleSelfUpdated: /* Tuple was already updated in current command? */ elog(ERROR, "tuple already updated by self"); break; case HeapTupleMayBeUpdated: /* done successfully */ #ifdef NOT_USED /* currently there are no indexes */ CatalogUpdateIndexes(lRel, rTuple); #endif break; case HeapTupleBeingUpdated: /* ignore uncommitted tuples */ break; case HeapTupleUpdated: /* ignore just-committed tuples */ break; default: elog(ERROR, "unrecognized heap_update status: %u", result); break; } } } } heap_endscan(scan); /* * We do NOT release the lock on pg_listener here; we need to hold it * until end of transaction (which is about to happen, anyway) to ensure * that notified backends see our tuple updates when they look. Else they * might disregard the signal, which would make the application programmer * very unhappy. */ heap_close(lRel, NoLock); ClearPendingNotifies(); if (Trace_notify) elog(DEBUG1, "AtCommit_Notify: done"); }
/* * Drop a table space * * Be careful to check that the tablespace is empty. */ void DropTableSpace(DropTableSpaceStmt *stmt) { #ifdef HAVE_SYMLINK char *tablespacename = stmt->tablespacename; HeapScanDesc scandesc; Relation rel; HeapTuple tuple; ScanKeyData entry[1]; Oid tablespaceoid; /* don't call this in a transaction block */ PreventTransactionChain((void *) stmt, "DROP TABLESPACE"); /* * Acquire ExclusiveLock on pg_tablespace to ensure that no one else is * trying to do DROP TABLESPACE or TablespaceCreateDbspace concurrently. */ rel = heap_open(TableSpaceRelationId, ExclusiveLock); /* * Find the target tuple */ ScanKeyInit(&entry[0], Anum_pg_tablespace_spcname, BTEqualStrategyNumber, F_NAMEEQ, CStringGetDatum(tablespacename)); scandesc = heap_beginscan(rel, SnapshotNow, 1, entry); tuple = heap_getnext(scandesc, ForwardScanDirection); if (!HeapTupleIsValid(tuple)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("tablespace \"%s\" does not exist", tablespacename))); 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); /* * Remove the pg_tablespace tuple (this will roll back if we fail below) */ simple_heap_delete(rel, &tuple->t_self); heap_endscan(scandesc); /* * Remove dependency on owner. */ deleteSharedDependencyRecordsFor(TableSpaceRelationId, tablespaceoid); /* * Try to remove the physical infrastructure */ if (!remove_tablespace_directories(tablespaceoid, false)) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("tablespace \"%s\" is not empty", tablespacename))); /* Record the filesystem change in XLOG */ { xl_tblspc_drop_rec xlrec; XLogRecData rdata[1]; xlrec.ts_id = tablespaceoid; rdata[0].data = (char *) &xlrec; rdata[0].len = sizeof(xl_tblspc_drop_rec); rdata[0].buffer = InvalidBuffer; rdata[0].next = NULL; (void) XLogInsert(RM_TBLSPC_ID, XLOG_TBLSPC_DROP, rdata); } /* We keep the lock on pg_tablespace until commit */ heap_close(rel, NoLock); #else /* !HAVE_SYMLINK */ ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("tablespaces are not supported on this platform"))); #endif /* HAVE_SYMLINK */ }
void inv_truncate(LargeObjectDesc *obj_desc, int len) { int32 pageno = (int32) (len / LOBLKSIZE); int off; ScanKeyData skey[2]; SysScanDesc sd; HeapTuple oldtuple; Form_pg_largeobject olddata; struct { bytea hdr; char data[LOBLKSIZE]; /* make struct big enough */ int32 align_it; /* ensure struct is aligned well enough */ } workbuf; char *workb = VARDATA(&workbuf.hdr); HeapTuple newtup; Datum values[Natts_pg_largeobject]; bool nulls[Natts_pg_largeobject]; bool replace[Natts_pg_largeobject]; CatalogIndexState indstate; Assert(PointerIsValid(obj_desc)); /* enforce writability because snapshot is probably wrong otherwise */ if ((obj_desc->flags & IFS_WRLOCK) == 0) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("large object %u was not opened for writing", obj_desc->id))); /* check existence of the target largeobject */ if (!LargeObjectExists(obj_desc->id)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("large object %u was already dropped", obj_desc->id))); open_lo_relation(); indstate = CatalogOpenIndexes(lo_heap_r); /* * Set up to find all pages with desired loid and pageno >= target */ ScanKeyInit(&skey[0], Anum_pg_largeobject_loid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(obj_desc->id)); ScanKeyInit(&skey[1], Anum_pg_largeobject_pageno, BTGreaterEqualStrategyNumber, F_INT4GE, Int32GetDatum(pageno)); sd = systable_beginscan_ordered(lo_heap_r, lo_index_r, obj_desc->snapshot, 2, skey); /* * If possible, get the page the truncation point is in. The truncation * point may be beyond the end of the LO or in a hole. */ olddata = NULL; if ((oldtuple = systable_getnext_ordered(sd, ForwardScanDirection)) != NULL) { if (HeapTupleHasNulls(oldtuple)) /* paranoia */ elog(ERROR, "null field found in pg_largeobject"); olddata = (Form_pg_largeobject) GETSTRUCT(oldtuple); Assert(olddata->pageno >= pageno); } /* * If we found the page of the truncation point we need to truncate the * data in it. Otherwise if we're in a hole, we need to create a page to * mark the end of data. */ if (olddata != NULL && olddata->pageno == pageno) { /* First, load old data into workbuf */ bytea *datafield = &(olddata->data); /* see note at top of * file */ bool pfreeit = false; int pagelen; if (VARATT_IS_EXTENDED(datafield)) { datafield = (bytea *) heap_tuple_untoast_attr((struct varlena *) datafield); pfreeit = true; } pagelen = getbytealen(datafield); Assert(pagelen <= LOBLKSIZE); memcpy(workb, VARDATA(datafield), pagelen); if (pfreeit) pfree(datafield); /* * Fill any hole */ off = len % LOBLKSIZE; if (off > pagelen) MemSet(workb + pagelen, 0, off - pagelen); /* compute length of new page */ SET_VARSIZE(&workbuf.hdr, off + VARHDRSZ); /* * Form and insert updated tuple */ memset(values, 0, sizeof(values)); memset(nulls, false, sizeof(nulls)); memset(replace, false, sizeof(replace)); values[Anum_pg_largeobject_data - 1] = PointerGetDatum(&workbuf); replace[Anum_pg_largeobject_data - 1] = true; newtup = heap_modify_tuple(oldtuple, RelationGetDescr(lo_heap_r), values, nulls, replace); simple_heap_update(lo_heap_r, &newtup->t_self, newtup); CatalogIndexInsert(indstate, newtup); heap_freetuple(newtup); } else { /* * If the first page we found was after the truncation point, we're in * a hole that we'll fill, but we need to delete the later page * because the loop below won't visit it again. */ if (olddata != NULL) { Assert(olddata->pageno > pageno); simple_heap_delete(lo_heap_r, &oldtuple->t_self); } /* * Write a brand new page. * * Fill the hole up to the truncation point */ off = len % LOBLKSIZE; if (off > 0) MemSet(workb, 0, off); /* compute length of new page */ SET_VARSIZE(&workbuf.hdr, off + VARHDRSZ); /* * Form and insert new tuple */ memset(values, 0, sizeof(values)); memset(nulls, false, sizeof(nulls)); values[Anum_pg_largeobject_loid - 1] = ObjectIdGetDatum(obj_desc->id); values[Anum_pg_largeobject_pageno - 1] = Int32GetDatum(pageno); values[Anum_pg_largeobject_data - 1] = PointerGetDatum(&workbuf); newtup = heap_form_tuple(lo_heap_r->rd_att, values, nulls); simple_heap_insert(lo_heap_r, newtup); CatalogIndexInsert(indstate, newtup); heap_freetuple(newtup); } /* * Delete any pages after the truncation point. If the initial search * didn't find a page, then of course there's nothing more to do. */ if (olddata != NULL) { while ((oldtuple = systable_getnext_ordered(sd, ForwardScanDirection)) != NULL) { simple_heap_delete(lo_heap_r, &oldtuple->t_self); } } systable_endscan_ordered(sd); CatalogCloseIndexes(indstate); /* * Advance command counter so that tuple updates will be seen by later * large-object operations in this transaction. */ CommandCounterIncrement(); }
/* * Adjust dependency record(s) to point to a different object of the same type * * classId/objectId specify the referencing object. * refClassId/oldRefObjectId specify the old referenced object. * newRefObjectId is the new referenced object (must be of class refClassId). * * Note the lack of objsubid parameters. If there are subobject references * they will all be readjusted. * * Returns the number of records updated. */ long changeDependencyFor(Oid classId, Oid objectId, Oid refClassId, Oid oldRefObjectId, Oid newRefObjectId) { long count = 0; Relation depRel; ScanKeyData key[2]; SysScanDesc scan; HeapTuple tup; ObjectAddress objAddr; bool newIsPinned; depRel = heap_open(DependRelationId, RowExclusiveLock); /* * If oldRefObjectId is pinned, there won't be any dependency entries on * it --- we can't cope in that case. (This isn't really worth expending * code to fix, in current usage; it just means you can't rename stuff out * of pg_catalog, which would likely be a bad move anyway.) */ objAddr.classId = refClassId; objAddr.objectId = oldRefObjectId; objAddr.objectSubId = 0; if (isObjectPinned(&objAddr, depRel)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot remove dependency on %s because it is a system object", getObjectDescription(&objAddr)))); /* * We can handle adding a dependency on something pinned, though, since * that just means deleting the dependency entry. */ objAddr.objectId = newRefObjectId; newIsPinned = isObjectPinned(&objAddr, depRel); /* Now search for dependency records */ ScanKeyInit(&key[0], Anum_pg_depend_classid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(classId)); ScanKeyInit(&key[1], Anum_pg_depend_objid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(objectId)); scan = systable_beginscan(depRel, DependDependerIndexId, true, NULL, 2, key); while (HeapTupleIsValid((tup = systable_getnext(scan)))) { Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup); if (depform->refclassid == refClassId && depform->refobjid == oldRefObjectId) { if (newIsPinned) simple_heap_delete(depRel, &tup->t_self); else { /* make a modifiable copy */ tup = heap_copytuple(tup); depform = (Form_pg_depend) GETSTRUCT(tup); depform->refobjid = newRefObjectId; simple_heap_update(depRel, &tup->t_self, tup); CatalogUpdateIndexes(depRel, tup); heap_freetuple(tup); } count++; } } systable_endscan(scan); heap_close(depRel, RowExclusiveLock); return count; }
void inv_truncate(struct lobj *obj_desc, int len) { int32 pageno = (int32) (len / LO_BLK_SIZE); int off; struct scankey skey[2]; struct sys_scan* sd; struct heap_tuple* oldtuple; Form_pg_largeobject olddata; struct { bytea hdr; char data[LO_BLK_SIZE]; /* make struct big enough */ int32 align_it;/* ensure struct is aligned well enough */ } workbuf; char* workb = VLA_DATA(&workbuf.hdr); struct heap_tuple* newtup; datum_t values[Natts_pg_largeobject]; bool nulls[Natts_pg_largeobject]; bool replace[Natts_pg_largeobject]; CatalogIndexState indstate; ASSERT(PTR_VALID(obj_desc)); /* enforce writability because snapshot is probably wrong otherwise */ if ((obj_desc->flags & IFS_WRLOCK) == 0) ereport(ERROR, ( errcode(E_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("large object %u was not opened for writing", obj_desc->id))); /* check existence of the target largeobject */ if (!large_obj_exists(obj_desc->id)) ereport(ERROR, ( errcode(E_UNDEFINED_OBJECT), errmsg("large object %u was already dropped", obj_desc->id))); open_lo_relation(); indstate = cat_open_indexes(lo_heap_r); /* * Set up to find all pages with desired loid and pageno >= target */ scankey_init(&skey[0], Anum_pg_largeobject_loid, BT_EQ_STRAT_NR, F_OIDEQ, OID_TO_D(obj_desc->id)); scankey_init(&skey[1], Anum_pg_largeobject_pageno, BT_GE_STRAT_NR, F_INT4GE, INT32_TO_D(pageno)); sd = systable_beginscan_ordered(lo_heap_r, lo_index_r, obj_desc->snapshot, 2, skey); /* * If possible, get the page the truncation point is in. The truncation * point may be beyond the end of the LO or in a hole. */ olddata = NULL; if ((oldtuple = systable_getnext_ordered(sd, FORWARD_SCANDIR)) != NULL) { if (HT_HAS_NULLS(oldtuple)) /* paranoia */ elog(ERROR, "null field found in pg_largeobject"); olddata = (Form_pg_largeobject) GET_STRUCT(oldtuple); ASSERT(olddata->pageno >= pageno); } /* * If we found the page of the truncation point we need to truncate the * data in it. Otherwise if we're in a hole, we need to create a page to * mark the end of data. */ if (olddata != NULL && olddata->pageno == pageno) { /* First, load old data into workbuf */ bytea* datafield = &(olddata->data); /* see note at top of file */ bool pfreeit = false; int pagelen; if (VLA_EXTENDED(datafield)) { datafield = (bytea *) heap_tuple_untoast_attr((struct vla *) datafield); pfreeit = true; } pagelen = getbytealen(datafield); ASSERT(pagelen <= LO_BLK_SIZE); memcpy(workb, VLA_DATA(datafield), pagelen); if (pfreeit) pfree(datafield); /* * Fill any hole */ off = len % LO_BLK_SIZE; if (off > pagelen) pg_memset(workb + pagelen, 0, off - pagelen); /* compute length of new page */ VLA_SET_SZ_STND(&workbuf.hdr, off + VAR_HDR_SZ); /* * Form and insert updated tuple */ memset(values, 0, sizeof(values)); memset(nulls, false, sizeof(nulls)); memset(replace, false, sizeof(replace)); values[Anum_pg_largeobject_data - 1] = PTR_TO_D(&workbuf); replace[Anum_pg_largeobject_data - 1] = true; newtup = heap_modify_tuple(oldtuple, REL_DESC(lo_heap_r), values, nulls, replace); simple_heap_update(lo_heap_r, &newtup->t_self, newtup); cat_index_insert(indstate, newtup); heap_free_tuple(newtup); } else { /* * If the first page we found was after the truncation point, we're in * a hole that we'll fill, but we need to delete the later page * because the loop below won't visit it again. */ if (olddata != NULL) { ASSERT(olddata->pageno > pageno); simple_heap_delete(lo_heap_r, &oldtuple->t_self); } /* * Write a brand new page. * * Fill the hole up to the truncation point */ off = len % LO_BLK_SIZE; if (off > 0) pg_memset(workb, 0, off); /* compute length of new page */ VLA_SET_SZ_STND(&workbuf.hdr, off + VAR_HDR_SZ); /* * Form and insert new tuple */ memset(values, 0, sizeof(values)); memset(nulls, false, sizeof(nulls)); values[Anum_pg_largeobject_loid - 1] = OID_TO_D(obj_desc->id); values[Anum_pg_largeobject_pageno - 1] = INT32_TO_D(pageno); values[Anum_pg_largeobject_data - 1] = PTR_TO_D(&workbuf); newtup = heap_form_tuple(lo_heap_r->rd_att, values, nulls); simple_heap_insert(lo_heap_r, newtup); cat_index_insert(indstate, newtup); heap_free_tuple(newtup); } /* * Delete any pages after the truncation point. If the initial search * didn't find a page, then of course there's nothing more to do. */ if (olddata != NULL) { while ((oldtuple = systable_getnext_ordered(sd, FORWARD_SCANDIR)) != NULL) { simple_heap_delete(lo_heap_r, &oldtuple->t_self); } } systable_endscan_ordered(sd); cat_close_indexes(indstate); /* * Advance command counter so that tuple updates will be seen by later * large-object operations in this transaction. */ cmd_count_incr(); }
/* * CatalogTupleDelete - do heap and indexing work for deleting a catalog tuple * * Delete the tuple identified by "tid" in the specified catalog. * * With Postgres heaps, there is no index work to do at deletion time; * cleanup will be done later by VACUUM. However, callers of this function * shouldn't have to know that; we'd like a uniform abstraction for all * catalog tuple changes. Hence, provide this currently-trivial wrapper. * * The abstraction is a bit leaky in that we don't provide an optimized * CatalogTupleDeleteWithInfo version, because there is currently nothing to * optimize. If we ever need that, rather than touching a lot of call sites, * it might be better to do something about caching CatalogIndexState. */ void CatalogTupleDelete(Relation heapRel, ItemPointer tid) { simple_heap_delete(heapRel, tid); }
/* * index_drop * * NOTE: this routine should now only be called through performDeletion(), * else associated dependencies won't be cleaned up. */ void index_drop(Oid indexId) { Oid heapId; Relation userHeapRelation; Relation userIndexRelation; Relation indexRelation; HeapTuple tuple; int i; Assert(OidIsValid(indexId)); /* * To drop an index safely, we must grab exclusive lock on its parent * table; otherwise there could be other backends using the index! * Exclusive lock on the index alone is insufficient because another * backend might be in the midst of devising a query plan that will * use the index. The parser and planner take care to hold an * appropriate lock on the parent table while working, but having them * hold locks on all the indexes too seems overly complex. We do grab * exclusive lock on the index too, just to be safe. Both locks must * be held till end of transaction, else other backends will still see * this index in pg_index. */ heapId = IndexGetRelation(indexId); userHeapRelation = heap_open(heapId, AccessExclusiveLock); userIndexRelation = index_open(indexId); LockRelation(userIndexRelation, AccessExclusiveLock); /* * fix RELATION relation */ DeleteRelationTuple(indexId); /* * fix ATTRIBUTE relation */ DeleteAttributeTuples(indexId); /* * fix INDEX relation */ indexRelation = heap_openr(IndexRelationName, RowExclusiveLock); tuple = SearchSysCache(INDEXRELID, ObjectIdGetDatum(indexId), 0, 0, 0); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for index %u", indexId); simple_heap_delete(indexRelation, &tuple->t_self); ReleaseSysCache(tuple); heap_close(indexRelation, RowExclusiveLock); /* * flush buffer cache and physically remove the file */ i = FlushRelationBuffers(userIndexRelation, (BlockNumber) 0); if (i < 0) elog(ERROR, "FlushRelationBuffers returned %d", i); smgrunlink(DEFAULT_SMGR, userIndexRelation); /* * We are presently too lazy to attempt to compute the new correct * value of relhasindex (the next VACUUM will fix it if necessary). So * there is no need to update the pg_class tuple for the owning * relation. But we must send out a shared-cache-inval notice on the * owning relation to ensure other backends update their relcache * lists of indexes. */ CacheInvalidateRelcache(heapId); /* * Close rels, but keep locks */ index_close(userIndexRelation); heap_close(userHeapRelation, NoLock); RelationForgetRelation(indexId); }
/* * Guts of rule deletion. */ void RemoveRewriteRuleById(Oid ruleOid) { Relation RewriteRelation; ScanKeyData skey[1]; SysScanDesc rcscan; Relation event_relation; HeapTuple tuple; Oid eventRelationOid; bool hasMoreRules; /* * Open the pg_rewrite relation. */ RewriteRelation = heap_open(RewriteRelationId, RowExclusiveLock); /* * Find the tuple for the target rule. */ ScanKeyInit(&skey[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(ruleOid)); rcscan = systable_beginscan(RewriteRelation, RewriteOidIndexId, true, SnapshotNow, 1, skey); tuple = systable_getnext(rcscan); 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 */ simple_heap_delete(RewriteRelation, &tuple->t_self); systable_endscan(rcscan); heap_close(RewriteRelation, RowExclusiveLock); /* * 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); }
void AlterSetting(Oid databaseid, Oid roleid, VariableSetStmt *setstmt) { char *valuestr; HeapTuple tuple; Relation rel; ScanKeyData scankey[2]; SysScanDesc scan; valuestr = ExtractSetVariableArgs(setstmt); /* Get the old tuple, if any. */ rel = heap_open(DbRoleSettingRelationId, RowExclusiveLock); ScanKeyInit(&scankey[0], Anum_pg_db_role_setting_setdatabase, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(databaseid)); ScanKeyInit(&scankey[1], Anum_pg_db_role_setting_setrole, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(roleid)); scan = systable_beginscan(rel, DbRoleSettingDatidRolidIndexId, true, NULL, 2, scankey); tuple = systable_getnext(scan); /* * There are three cases: * * - in RESET ALL, request GUC to reset the settings array and update the * catalog if there's anything left, delete it otherwise * * - in other commands, if there's a tuple in pg_db_role_setting, update * it; if it ends up empty, delete it * * - otherwise, insert a new___ pg_db_role_setting tuple, but only if the * command is not RESET */ if (setstmt->kind == VAR_RESET_ALL) { if (HeapTupleIsValid(tuple)) { ArrayType *new___ = NULL; Datum datum; bool isnull; datum = heap_getattr(tuple, Anum_pg_db_role_setting_setconfig, RelationGetDescr(rel), &isnull); if (!isnull) new___ = GUCArrayReset(DatumGetArrayTypeP(datum)); if (new___) { Datum repl_val[Natts_pg_db_role_setting]; bool repl_null[Natts_pg_db_role_setting]; bool repl_repl[Natts_pg_db_role_setting]; HeapTuple newtuple; memset(repl_repl, false, sizeof(repl_repl)); repl_val[Anum_pg_db_role_setting_setconfig - 1] = PointerGetDatum(new___); repl_repl[Anum_pg_db_role_setting_setconfig - 1] = true; repl_null[Anum_pg_db_role_setting_setconfig - 1] = false; newtuple = heap_modify_tuple(tuple, RelationGetDescr(rel), repl_val, repl_null, repl_repl); simple_heap_update(rel, &tuple->t_self, newtuple); /* Update indexes */ CatalogUpdateIndexes(rel, newtuple); } else simple_heap_delete(rel, &tuple->t_self); } } else if (HeapTupleIsValid(tuple)) { Datum repl_val[Natts_pg_db_role_setting]; bool repl_null[Natts_pg_db_role_setting]; bool repl_repl[Natts_pg_db_role_setting]; HeapTuple newtuple; Datum datum; bool isnull; ArrayType *a; memset(repl_repl, false, sizeof(repl_repl)); repl_repl[Anum_pg_db_role_setting_setconfig - 1] = true; repl_null[Anum_pg_db_role_setting_setconfig - 1] = false; /* Extract old value of setconfig */ datum = heap_getattr(tuple, Anum_pg_db_role_setting_setconfig, RelationGetDescr(rel), &isnull); a = isnull ? NULL : DatumGetArrayTypeP(datum); /* Update (valuestr is NULL in RESET cases) */ if (valuestr) a = GUCArrayAdd(a, setstmt->name, valuestr); else a = GUCArrayDelete(a, setstmt->name); if (a) { repl_val[Anum_pg_db_role_setting_setconfig - 1] = PointerGetDatum(a); newtuple = heap_modify_tuple(tuple, RelationGetDescr(rel), repl_val, repl_null, repl_repl); simple_heap_update(rel, &tuple->t_self, newtuple); /* Update indexes */ CatalogUpdateIndexes(rel, newtuple); } else simple_heap_delete(rel, &tuple->t_self); } else if (valuestr) { /* non-null valuestr means it's not RESET, so insert a new___ tuple */ HeapTuple newtuple; Datum values[Natts_pg_db_role_setting]; bool nulls[Natts_pg_db_role_setting]; ArrayType *a; memset(nulls, false, sizeof(nulls)); a = GUCArrayAdd(NULL, setstmt->name, valuestr); values[Anum_pg_db_role_setting_setdatabase - 1] = ObjectIdGetDatum(databaseid); values[Anum_pg_db_role_setting_setrole - 1] = ObjectIdGetDatum(roleid); values[Anum_pg_db_role_setting_setconfig - 1] = PointerGetDatum(a); newtuple = heap_form_tuple(RelationGetDescr(rel), values, nulls); simple_heap_insert(rel, newtuple); /* Update indexes */ CatalogUpdateIndexes(rel, newtuple); } InvokeObjectPostAlterHookArg(DbRoleSettingRelationId, databaseid, 0, roleid, false); systable_endscan(scan); /* Close pg_db_role_setting, but keep lock till commit */ heap_close(rel, NoLock); }