/*------------------------------------------------------------------------- * * command.c * random postgres portal and utility support code * * Portions Copyright (c) 1996-2000, PostgreSQL, Inc * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * $Header: /cvsroot/pgsql/src/backend/commands/Attic/command.c,v 1.87 2000/07/05 16:17:38 wieck Exp $ * * NOTES * The PerformAddAttribute() code, like most of the relation * manipulating code in the commands/ directory, should go * someplace closer to the lib/catalog code. * *------------------------------------------------------------------------- */ #include "postgres.h" #include "catalog/catalog.h" #include "catalog/catname.h" #include "catalog/index.h" #include "catalog/indexing.h" #include "catalog/pg_attrdef.h" #include "catalog/pg_opclass.h" #include "commands/command.h" #include "executor/spi.h" #include "catalog/heap.h" #include "miscadmin.h" #include "optimizer/prep.h" #include "utils/acl.h" #include "utils/fmgroids.h" #include "commands/trigger.h" #ifdef _DROP_COLUMN_HACK__ #include "catalog/pg_index.h" #include "parser/parse.h" #endif /* _DROP_COLUMN_HACK__ */ #include "access/genam.h" /* -------------------------------- * PortalCleanup * -------------------------------- */ void PortalCleanup(Portal portal) { MemoryContext oldcontext; /* ---------------- * sanity checks * ---------------- */ AssertArg(PortalIsValid(portal)); AssertArg(portal->cleanup == PortalCleanup); /* ---------------- * set proper portal-executor context before calling ExecMain. * ---------------- */ oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal)); /* ---------------- * tell the executor to shutdown the query * ---------------- */ ExecutorEnd(PortalGetQueryDesc(portal), PortalGetState(portal)); /* ---------------- * switch back to previous context * ---------------- */ MemoryContextSwitchTo(oldcontext); } /* -------------------------------- * PerformPortalFetch * -------------------------------- */ void PerformPortalFetch(char *name, bool forward, int count, char *tag, CommandDest dest) { Portal portal; int feature; QueryDesc *queryDesc; MemoryContext oldcontext; Const limcount; /* ---------------- * sanity checks * ---------------- */ if (name == NULL) { elog(NOTICE, "PerformPortalFetch: missing portal name"); return; } /* ---------------- * Create a const node from the given count value * ---------------- */ memset(&limcount, 0, sizeof(limcount)); limcount.type = T_Const; limcount.consttype = INT4OID; limcount.constlen = sizeof(int4); limcount.constvalue = Int32GetDatum(count); limcount.constisnull = false; limcount.constbyval = true; limcount.constisset = false; limcount.constiscast = false; /* ---------------- * get the portal from the portal name * ---------------- */ portal = GetPortalByName(name); if (!PortalIsValid(portal)) { elog(NOTICE, "PerformPortalFetch: portal \"%s\" not found", name); return; } /* ---------------- * switch into the portal context * ---------------- */ oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal)); /* ---------------- * setup "feature" to tell the executor what direction and * how many tuples to fetch. * ---------------- */ if (forward) feature = EXEC_FOR; else feature = EXEC_BACK; /* ---------------- * tell the destination to prepare to recieve some tuples * ---------------- */ queryDesc = PortalGetQueryDesc(portal); if (dest == None) /* MOVE */ { QueryDesc *qdesc = (QueryDesc *) palloc(sizeof(QueryDesc)); memcpy(qdesc, queryDesc, sizeof(QueryDesc)); qdesc->dest = dest; queryDesc = qdesc; } BeginCommand(name, queryDesc->operation, portal->attinfo, /* QueryDescGetTypeInfo(queryDesc) */ false, /* portal fetches don't end up in * relations */ false, /* this is a portal fetch, not a "retrieve * portal" */ tag, dest); /* ---------------- * execute the portal fetch operation * ---------------- */ ExecutorRun(queryDesc, PortalGetState(portal), feature, (Node *) NULL, (Node *) &limcount); if (dest == None) /* MOVE */ pfree(queryDesc); /* ---------------- * Switch back to old context. * ---------------- */ MemoryContextSwitchTo(oldcontext); /* ---------------- * Note: the "end-of-command" tag is returned by higher-level * utility code * ---------------- */ } /* -------------------------------- * PerformPortalClose * -------------------------------- */ void PerformPortalClose(char *name, CommandDest dest) { Portal portal; /* ---------------- * sanity checks * ---------------- */ if (name == NULL) { elog(NOTICE, "PerformPortalClose: missing portal name"); return; } /* ---------------- * get the portal from the portal name * ---------------- */ portal = GetPortalByName(name); if (!PortalIsValid(portal)) { elog(NOTICE, "PerformPortalClose: portal \"%s\" not found", name); return; } /* ---------------- * Note: PortalCleanup is called as a side-effect * ---------------- */ PortalDrop(&portal); } /* ---------------- * AlterTableAddColumn * (formerly known as PerformAddAttribute) * * adds an additional attribute to a relation * * Adds attribute field(s) to a relation. Each new attribute * is given attnums in sequential order and is added to the * ATTRIBUTE relation. If the AMI fails, defunct tuples will * remain in the ATTRIBUTE relation for later vacuuming. * Later, there may be some reserved attribute names??? * * (If needed, can instead use elog to handle exceptions.) * * Note: * Initial idea of ordering the tuple attributes so that all * the variable length domains occured last was scratched. Doing * so would not speed access too much (in general) and would create * many complications in formtuple, amgetattr, and addattribute. * * scan attribute catalog for name conflict (within rel) * scan type catalog for absence of data type (if not arg) * create attnum magically??? * create attribute tuple * insert attribute in attribute catalog * modify reldesc * create new relation tuple * insert new relation in relation catalog * delete original relation from relation catalog * ---------------- */ void AlterTableAddColumn(const char *relationName, bool inherits, ColumnDef *colDef) { Relation rel, attrdesc; Oid myrelid; HeapTuple reltup; HeapTuple attributeTuple; Form_pg_attribute attribute; FormData_pg_attribute attributeD; int i; int minattnum, maxatts; HeapTuple tup; Relation idescs[Num_pg_attr_indices]; Relation ridescs[Num_pg_class_indices]; bool hasindex; /* * permissions checking. this would normally be done in utility.c, * but this particular routine is recursive. * * normally, only the owner of a class can change its schema. */ if (!allowSystemTableMods && IsSystemRelationName(relationName)) elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog", relationName); #ifndef NO_SECURITY if (!pg_ownercheck(UserName, relationName, RELNAME)) elog(ERROR, "ALTER TABLE: permission denied"); #endif /* * Grab an exclusive lock on the target table, which we will NOT * release until end of transaction. */ rel = heap_openr(relationName, AccessExclusiveLock); myrelid = RelationGetRelid(rel); heap_close(rel, NoLock); /* close rel but keep lock! */ /* * we can't add a not null attribute */ if (colDef->is_not_null) elog(ERROR, "Can't add a NOT NULL attribute to an existing relation"); if (colDef->raw_default || colDef->cooked_default) elog(ERROR, "Adding columns with defaults is not implemented."); /* * if the first element in the 'schema' list is a "*" then we are * supposed to add this attribute to all classes that inherit from * 'relationName' (as well as to 'relationName'). * * any permissions or problems with duplicate attributes will cause the * whole transaction to abort, which is what we want -- all or * nothing. */ if (colDef != NULL) { if (inherits) { List *child, *children; /* this routine is actually in the planner */ children = find_all_inheritors(myrelid); /* * find_all_inheritors does the recursive search of the * inheritance hierarchy, so all we have to do is process all * of the relids in the list that it returns. */ foreach(child, children) { Oid childrelid = lfirsti(child); char *childrelname; if (childrelid == myrelid) continue; rel = heap_open(childrelid, AccessExclusiveLock); childrelname = pstrdup(RelationGetRelationName(rel)); heap_close(rel, AccessExclusiveLock); AlterTableAddColumn(childrelname, false, colDef); pfree(childrelname); } } } rel = heap_openr(RelationRelationName, RowExclusiveLock); reltup = SearchSysCacheTupleCopy(RELNAME, PointerGetDatum(relationName), 0, 0, 0); if (!HeapTupleIsValid(reltup)) elog(ERROR, "ALTER TABLE: relation \"%s\" not found", relationName); /* * XXX is the following check sufficient? */ if (((Form_pg_class) GETSTRUCT(reltup))->relkind != RELKIND_RELATION) { elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", relationName); } minattnum = ((Form_pg_class) GETSTRUCT(reltup))->relnatts; maxatts = minattnum + 1; if (maxatts > MaxHeapAttributeNumber) elog(ERROR, "ALTER TABLE: relations limited to %d columns", MaxHeapAttributeNumber); attrdesc = heap_openr(AttributeRelationName, RowExclusiveLock); /* * Open all (if any) pg_attribute indices */ hasindex = RelationGetForm(attrdesc)->relhasindex; if (hasindex) CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, idescs); attributeD.attrelid = reltup->t_data->t_oid; attributeTuple = heap_addheader(Natts_pg_attribute, sizeof attributeD, (char *) &attributeD); attribute = (Form_pg_attribute) GETSTRUCT(attributeTuple); i = 1 + minattnum; { HeapTuple typeTuple; Form_pg_type tform; char *typename; int attnelems; tup = SearchSysCacheTuple(ATTNAME, ObjectIdGetDatum(reltup->t_data->t_oid), PointerGetDatum(colDef->colname), 0, 0); if (HeapTupleIsValid(tup)) elog(ERROR, "ALTER TABLE: column name \"%s\" already exists in table \"%s\"", colDef->colname, relationName); /* * check to see if it is an array attribute. */ typename = colDef->typename->name; if (colDef->typename->arrayBounds) { attnelems = length(colDef->typename->arrayBounds); typename = makeArrayTypeName(colDef->typename->name); } else attnelems = 0; typeTuple = SearchSysCacheTuple(TYPENAME, PointerGetDatum(typename), 0, 0, 0); tform = (Form_pg_type) GETSTRUCT(typeTuple); if (!HeapTupleIsValid(typeTuple)) elog(ERROR, "ALTER TABLE: type \"%s\" does not exist", typename); namestrcpy(&(attribute->attname), colDef->colname); attribute->atttypid = typeTuple->t_data->t_oid; attribute->attlen = tform->typlen; attribute->attdisbursion = 0; attribute->attcacheoff = -1; attribute->atttypmod = colDef->typename->typmod; attribute->attnum = i; attribute->attbyval = tform->typbyval; attribute->attnelems = attnelems; attribute->attisset = (bool) (tform->typtype == 'c'); attribute->attstorage = tform->typstorage; attribute->attalign = tform->typalign; attribute->attnotnull = false; attribute->atthasdef = (colDef->raw_default != NULL || colDef->cooked_default != NULL); heap_insert(attrdesc, attributeTuple); if (hasindex) CatalogIndexInsert(idescs, Num_pg_attr_indices, attrdesc, attributeTuple); } if (hasindex) CatalogCloseIndices(Num_pg_attr_indices, idescs); heap_close(attrdesc, RowExclusiveLock); ((Form_pg_class) GETSTRUCT(reltup))->relnatts = maxatts; heap_update(rel, &reltup->t_self, reltup, NULL); /* keep catalog indices current */ CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, ridescs); CatalogIndexInsert(ridescs, Num_pg_class_indices, rel, reltup); CatalogCloseIndices(Num_pg_class_indices, ridescs); heap_freetuple(reltup); heap_close(rel, NoLock); /* * Automatically create the secondary relation for TOAST * if it formerly had no such but now has toastable attributes. */ CommandCounterIncrement(); AlterTableCreateToastTable(relationName, true); } static void drop_default(Oid relid, int16 attnum); /* * ALTER TABLE ALTER COLUMN SET/DROP DEFAULT */ void AlterTableAlterColumn(const char *relationName, bool inh, const char *colName, Node *newDefault) { Relation rel; HeapTuple tuple; int16 attnum; Oid myrelid; if (!allowSystemTableMods && IsSystemRelationName(relationName)) elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog", relationName); #ifndef NO_SECURITY if (!pg_ownercheck(UserName, relationName, RELNAME)) elog(ERROR, "ALTER TABLE: permission denied"); #endif rel = heap_openr(relationName, AccessExclusiveLock); myrelid = RelationGetRelid(rel); heap_close(rel, NoLock); /* * Propagate to children if desired */ if (inh) { List *child, *children; /* this routine is actually in the planner */ children = find_all_inheritors(myrelid); /* * find_all_inheritors does the recursive search of the * inheritance hierarchy, so all we have to do is process all of * the relids in the list that it returns. */ foreach(child, children) { Oid childrelid = lfirsti(child); if (childrelid == myrelid) continue; rel = heap_open(childrelid, AccessExclusiveLock); AlterTableAlterColumn(RelationGetRelationName(rel), false, colName, newDefault); heap_close(rel, AccessExclusiveLock); } } /* -= now do the thing on this relation =- */ /* reopen the business */ rel = heap_openr((char *) relationName, AccessExclusiveLock); /* * get the number of the attribute */ tuple = SearchSysCacheTuple(ATTNAME, ObjectIdGetDatum(myrelid), NameGetDatum(namein((char *) colName)), 0, 0); if (!HeapTupleIsValid(tuple)) { heap_close(rel, AccessExclusiveLock); elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"", relationName, colName); } attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum; if (newDefault) /* SET DEFAULT */ { List *rawDefaults = NIL; RawColumnDefault *rawEnt; /* Get rid of the old one first */ drop_default(myrelid, attnum); rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault)); rawEnt->attnum = attnum; rawEnt->raw_default = newDefault; rawDefaults = lappend(rawDefaults, rawEnt); /* * This function is intended for CREATE TABLE, so it processes a * _list_ of defaults, but we just do one. */ AddRelationRawConstraints(rel, rawDefaults, NIL); } else /* DROP DEFAULT */ { Relation attr_rel; ScanKeyData scankeys[3]; HeapScanDesc scan; HeapTuple tuple; attr_rel = heap_openr(AttributeRelationName, AccessExclusiveLock); ScanKeyEntryInitialize(&scankeys[0], 0x0, Anum_pg_attribute_attrelid, F_OIDEQ, ObjectIdGetDatum(myrelid)); ScanKeyEntryInitialize(&scankeys[1], 0x0, Anum_pg_attribute_attnum, F_INT2EQ, Int16GetDatum(attnum)); ScanKeyEntryInitialize(&scankeys[2], 0x0, Anum_pg_attribute_atthasdef, F_BOOLEQ, Int32GetDatum(TRUE)); scan = heap_beginscan(attr_rel, false, SnapshotNow, 3, scankeys); AssertState(scan != NULL); if (HeapTupleIsValid(tuple = heap_getnext(scan, 0))) { HeapTuple newtuple; Relation irelations[Num_pg_attr_indices]; /* update to false */ newtuple = heap_copytuple(tuple); ((Form_pg_attribute) GETSTRUCT(newtuple))->atthasdef = FALSE; heap_update(attr_rel, &tuple->t_self, newtuple, NULL); /* keep the system catalog indices current */ CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, irelations); CatalogIndexInsert(irelations, Num_pg_attr_indices, attr_rel, newtuple); CatalogCloseIndices(Num_pg_attr_indices, irelations); /* get rid of actual default definition */ drop_default(myrelid, attnum); } heap_endscan(scan); heap_close(attr_rel, NoLock); } heap_close(rel, NoLock); } static void drop_default(Oid relid, int16 attnum) { ScanKeyData scankeys[2]; HeapScanDesc scan; Relation attrdef_rel; HeapTuple tuple; attrdef_rel = heap_openr(AttrDefaultRelationName, AccessExclusiveLock); ScanKeyEntryInitialize(&scankeys[0], 0x0, Anum_pg_attrdef_adrelid, F_OIDEQ, ObjectIdGetDatum(relid)); ScanKeyEntryInitialize(&scankeys[1], 0x0, Anum_pg_attrdef_adnum, F_INT2EQ, Int16GetDatum(attnum)); scan = heap_beginscan(attrdef_rel, false, SnapshotNow, 2, scankeys); AssertState(scan != NULL); if (HeapTupleIsValid(tuple = heap_getnext(scan, 0))) heap_delete(attrdef_rel, &tuple->t_self, NULL); heap_endscan(scan); heap_close(attrdef_rel, NoLock); } #ifdef _DROP_COLUMN_HACK__ /* * ALTER TABLE DROP COLUMN trial implementation * */ /* * system table scan(index scan/sequential scan) */ typedef struct SysScanDescData { Relation heap_rel; Relation irel; HeapScanDesc scan; IndexScanDesc iscan; HeapTupleData tuple; Buffer buffer; } SysScanDescData, *SysScanDesc; static void * systable_beginscan(Relation rel, const char *indexRelname, int nkeys, ScanKey entry) { bool hasindex = (rel->rd_rel->relhasindex && !IsIgnoringSystemIndexes()); SysScanDesc sysscan; sysscan = (SysScanDesc) palloc(sizeof(SysScanDescData)); sysscan->heap_rel = rel; sysscan->irel = (Relation) NULL; sysscan->tuple.t_datamcxt = NULL; sysscan->tuple.t_data = NULL; sysscan->buffer = InvalidBuffer; if (hasindex) { sysscan->irel = index_openr((char *) indexRelname); sysscan->iscan = index_beginscan(sysscan->irel, false, nkeys, entry); } else sysscan->scan = heap_beginscan(rel, false, SnapshotNow, nkeys, entry); return (void *) sysscan; } static void systable_endscan(void *scan) { SysScanDesc sysscan = (SysScanDesc) scan; if (sysscan->irel) { if (BufferIsValid(sysscan->buffer)) ReleaseBuffer(sysscan->buffer); index_endscan(sysscan->iscan); index_close(sysscan->irel); } else heap_endscan(sysscan->scan); pfree(scan); } static HeapTuple systable_getnext(void *scan) { SysScanDesc sysscan = (SysScanDesc) scan; HeapTuple htup = (HeapTuple) NULL; RetrieveIndexResult indexRes; if (sysscan->irel) { if (BufferIsValid(sysscan->buffer)) { ReleaseBuffer(sysscan->buffer); sysscan->buffer = InvalidBuffer; } while (indexRes = index_getnext(sysscan->iscan, ForwardScanDirection), indexRes != NULL) { sysscan->tuple.t_self = indexRes->heap_iptr; heap_fetch(sysscan->heap_rel, SnapshotNow, &sysscan->tuple, &(sysscan->buffer)); pfree(indexRes); if (sysscan->tuple.t_data != NULL) { htup = &sysscan->tuple; break; } } } else htup = heap_getnext(sysscan->scan, 0); return htup; } /* * find a specified attribute in a node entry */ static bool find_attribute_walker(Node *node, int attnum) { if (node == NULL) return false; if (IsA(node, Var)) { Var *var = (Var *) node; if (var->varlevelsup == 0 && var->varno == 1 && var->varattno == attnum) return true; } return expression_tree_walker(node, find_attribute_walker, (void *) attnum); } static bool find_attribute_in_node(Node *node, int attnum) { return expression_tree_walker(node, find_attribute_walker, (void *) attnum); } /* * Remove/check references for the column */ static bool RemoveColumnReferences(Oid reloid, int attnum, bool checkonly, HeapTuple reltup) { Relation indexRelation, rcrel; ScanKeyData entry; HeapScanDesc scan; void *sysscan; HeapTuple htup, indexTuple; Form_pg_index index; Form_pg_relcheck relcheck; Form_pg_class pgcform = (Form_pg_class) NULL; int i; bool checkok = true; if (!checkonly) pgcform = (Form_pg_class) GETSTRUCT(reltup); /* * Remove/check constraints here */ ScanKeyEntryInitialize(&entry, (bits16) 0x0, Anum_pg_relcheck_rcrelid, (RegProcedure) F_OIDEQ, ObjectIdGetDatum(reloid)); rcrel = heap_openr(RelCheckRelationName, RowExclusiveLock); sysscan = systable_beginscan(rcrel, RelCheckIndex, 1, &entry); while (HeapTupleIsValid(htup = systable_getnext(sysscan))) { char *ccbin; Node *node; relcheck = (Form_pg_relcheck) GETSTRUCT(htup); ccbin = textout(&relcheck->rcbin); if (!ccbin) continue; node = stringToNode(ccbin); pfree(ccbin); if (find_attribute_in_node(node, attnum)) { if (checkonly) { checkok = false; elog(ERROR, "target column is used in a constraint"); } else { heap_delete(rcrel, &htup->t_self, NULL); pgcform->relchecks--; } } } systable_endscan(sysscan); heap_close(rcrel, NoLock); /* * What to do with triggers/rules/views/procedues ? */ /* * Remove/check indexes */ indexRelation = heap_openr(IndexRelationName, RowExclusiveLock); ScanKeyEntryInitialize(&entry, 0, Anum_pg_index_indrelid, F_OIDEQ, ObjectIdGetDatum(reloid)); scan = heap_beginscan(indexRelation, false, SnapshotNow, 1, &entry); while (HeapTupleIsValid(indexTuple = heap_getnext(scan, 0))) { index = (Form_pg_index) GETSTRUCT(indexTuple); for (i = 0; i < INDEX_MAX_KEYS; i++) { if (index->indkey[i] == InvalidAttrNumber) break; else if (index->indkey[i] == attnum) { if (checkonly) { checkok = false; elog(ERROR, "target column is used in an index"); } else { htup = SearchSysCacheTuple(RELOID, ObjectIdGetDatum(index->indexrelid), 0, 0, 0); RemoveIndex(NameStr(((Form_pg_class) GETSTRUCT(htup))->relname)); } break; } } } heap_endscan(scan); heap_close(indexRelation, NoLock); return checkok; } #endif /* _DROP_COLUMN_HACK__ */ /* * ALTER TABLE DROP COLUMN */ void AlterTableDropColumn(const char *relationName, bool inh, const char *colName, int behavior) { #ifdef _DROP_COLUMN_HACK__ Relation rel, attrdesc, adrel; Oid myrelid, attoid; HeapTuple reltup; HeapTupleData classtuple; Buffer buffer; Form_pg_attribute attribute; HeapTuple tup; Relation idescs[Num_pg_attr_indices]; int attnum; bool hasindex; char dropColname[32]; void *sysscan; ScanKeyData scankeys[2]; if (inh) elog(ERROR, "ALTER TABLE / DROP COLUMN with inherit option is not supported yet"); /* * permissions checking. this would normally be done in utility.c, * but this particular routine is recursive. * * normally, only the owner of a class can change its schema. */ if (!allowSystemTableMods && IsSystemRelationName(relationName)) elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog", relationName); #ifndef NO_SECURITY if (!pg_ownercheck(UserName, relationName, RELNAME)) elog(ERROR, "ALTER TABLE: permission denied"); #endif /* * Grab an exclusive lock on the target table, which we will NOT * release until end of transaction. */ rel = heap_openr(relationName, AccessExclusiveLock); myrelid = RelationGetRelid(rel); heap_close(rel, NoLock); /* close rel but keep lock! */ /* * What to do when rel has inheritors ? */ if (length(find_all_inheritors(myrelid)) > 1) elog(ERROR, "ALTER TABLE: cannot drop a column on table that is inherited from"); /* * lock the pg_class tuple for update */ reltup = SearchSysCacheTuple(RELNAME, PointerGetDatum(relationName), 0, 0, 0); if (!HeapTupleIsValid(reltup)) elog(ERROR, "ALTER TABLE: relation \"%s\" not found", relationName); rel = heap_openr(RelationRelationName, RowExclusiveLock); classtuple.t_self = reltup->t_self; switch (heap_mark4update(rel, &classtuple, &buffer)) { case HeapTupleSelfUpdated: case HeapTupleMayBeUpdated: break; default: elog(ERROR, "couldn't lock pg_class tuple"); } reltup = heap_copytuple(&classtuple); ReleaseBuffer(buffer); /* * XXX is the following check sufficient? */ if (((Form_pg_class) GETSTRUCT(reltup))->relkind != RELKIND_RELATION) { elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", relationName); } attrdesc = heap_openr(AttributeRelationName, RowExclusiveLock); /* * Get the target pg_attribute tuple */ tup = SearchSysCacheTupleCopy(ATTNAME, ObjectIdGetDatum(reltup->t_data->t_oid), PointerGetDatum(colName), 0, 0); if (!HeapTupleIsValid(tup)) elog(ERROR, "ALTER TABLE: column name \"%s\" doesn't exist in table \"%s\"", colName, relationName); attribute = (Form_pg_attribute) GETSTRUCT(tup); if (attribute->attnum <= 0) elog(ERROR, "ALTER TABLE: column name \"%s\" was already dropped", colName); attnum = attribute->attnum; attoid = tup->t_data->t_oid; /* * Check constraints/indices etc here */ if (behavior != CASCADE) { if (!RemoveColumnReferences(myrelid, attnum, true, NULL)) elog(ERROR, "the column is referenced"); } /* * change the target pg_attribute tuple */ sprintf(dropColname, "*already Dropped*%d", attnum); namestrcpy(&(attribute->attname), dropColname); ATTRIBUTE_DROP_COLUMN(attribute); heap_update(attrdesc, &tup->t_self, tup, NULL); hasindex = (!IsIgnoringSystemIndexes() && RelationGetForm(attrdesc)->relhasindex); if (hasindex) { CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, idescs); CatalogIndexInsert(idescs, Num_pg_attr_indices, attrdesc, tup); CatalogCloseIndices(Num_pg_attr_indices, idescs); } heap_close(attrdesc, NoLock); heap_freetuple(tup); /* delete comments */ DeleteComments(attoid); /* delete attrdef */ adrel = heap_openr(AttrDefaultRelationName, RowExclusiveLock); ScanKeyEntryInitialize(&scankeys[0], 0x0, Anum_pg_attrdef_adrelid, F_OIDEQ, ObjectIdGetDatum(myrelid)); /* * Oops pg_attrdef doesn't have (adrelid,adnum) index * ScanKeyEntryInitialize(&scankeys[1], 0x0, Anum_pg_attrdef_adnum, * F_INT2EQ, Int16GetDatum(attnum)); sysscan = * systable_beginscan(adrel, AttrDefaultIndex, 2, scankeys); */ sysscan = systable_beginscan(adrel, AttrDefaultIndex, 1, scankeys); while (HeapTupleIsValid(tup = systable_getnext(sysscan))) { if (((Form_pg_attrdef) GETSTRUCT(tup))->adnum == attnum) { heap_delete(adrel, &tup->t_self, NULL); break; } } systable_endscan(sysscan); heap_close(adrel, NoLock); /* * Remove objects which reference this column */ if (behavior == CASCADE) { Relation ridescs[Num_pg_class_indices]; RemoveColumnReferences(myrelid, attnum, false, reltup); /* update pg_class tuple */ heap_update(rel, &reltup->t_self, reltup, NULL); CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, ridescs); CatalogIndexInsert(ridescs, Num_pg_class_indices, rel, reltup); CatalogCloseIndices(Num_pg_class_indices, ridescs); } heap_freetuple(reltup); heap_close(rel, NoLock); #else elog(ERROR, "ALTER TABLE / DROP COLUMN is not implemented"); #endif /* _DROP_COLUMN_HACK__ */ } /* * ALTER TABLE ADD CONSTRAINT */ void AlterTableAddConstraint(const char *relationName, bool inh, Node *newConstraint) { if (newConstraint == NULL) elog(ERROR, "ALTER TABLE / ADD CONSTRAINT passed invalid constraint."); switch (nodeTag(newConstraint)) { case T_Constraint: elog(ERROR, "ALTER TABLE / ADD CONSTRAINT is not implemented"); case T_FkConstraint: { FkConstraint *fkconstraint = (FkConstraint *) newConstraint; Relation rel; HeapScanDesc scan; HeapTuple tuple; Trigger trig; List *list; int count; /* * Grab an exclusive lock on the pk table, so that someone * doesn't delete rows out from under us. */ rel = heap_openr(fkconstraint->pktable_name, AccessExclusiveLock); heap_close(rel, NoLock); /* * Grab an exclusive lock on the fk table, and then scan * through each tuple, calling the RI_FKey_Match_Ins * (insert trigger) as if that tuple had just been * inserted. If any of those fail, it should elog(ERROR) * and that's that. */ rel = heap_openr(relationName, AccessExclusiveLock); trig.tgoid = 0; trig.tgname = ""; trig.tgfoid = 0; trig.tgtype = 0; trig.tgenabled = TRUE; trig.tgisconstraint = TRUE; trig.tginitdeferred = FALSE; trig.tgdeferrable = FALSE; trig.tgargs = (char **) palloc( sizeof(char *) * (4 + length(fkconstraint->fk_attrs) + length(fkconstraint->pk_attrs))); trig.tgargs[0] = ""; trig.tgargs[1] = (char *) relationName; trig.tgargs[2] = fkconstraint->pktable_name; trig.tgargs[3] = fkconstraint->match_type; count = 4; foreach(list, fkconstraint->fk_attrs) { Ident *fk_at = lfirst(list); trig.tgargs[count++] = fk_at->name; } foreach(list, fkconstraint->pk_attrs) { Ident *pk_at = lfirst(list); trig.tgargs[count++] = pk_at->name; } trig.tgnargs = count; scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL); AssertState(scan != NULL); while (HeapTupleIsValid(tuple = heap_getnext(scan, 0))) { /* Make a call to the check function */ /* No parameters are passed, but we do set a context */ FunctionCallInfoData fcinfo; TriggerData trigdata; MemSet(&fcinfo, 0, sizeof(fcinfo)); /* We assume RI_FKey_check_ins won't look at flinfo... */ trigdata.type = T_TriggerData; trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW; trigdata.tg_relation = rel; trigdata.tg_trigtuple = tuple; trigdata.tg_newtuple = NULL; trigdata.tg_trigger = &trig; fcinfo.context = (Node *) &trigdata; RI_FKey_check_ins(&fcinfo); } heap_endscan(scan); heap_close(rel, NoLock); /* close rel but keep * lock! */ pfree(trig.tgargs); } break; default: elog(ERROR, "ALTER TABLE / ADD CONSTRAINT unable to determine type of constraint passed"); } } /* * ALTER TABLE DROP CONSTRAINT */ void AlterTableDropConstraint(const char *relationName, bool inh, const char *constrName, int behavior) { elog(ERROR, "ALTER TABLE / DROP CONSTRAINT is not implemented"); } /* * ALTER TABLE CREATE TOAST TABLE */ void AlterTableCreateToastTable(const char *relationName, bool silent) { Relation rel; Oid myrelid; HeapTuple reltup; HeapTupleData classtuple; TupleDesc tupdesc; Form_pg_attribute *att; Relation class_rel; Buffer buffer; Relation ridescs[Num_pg_class_indices]; Oid toast_relid; Oid toast_idxid; bool has_toastable_attrs = false; int i; char toast_relname[NAMEDATALEN + 1]; char toast_idxname[NAMEDATALEN + 1]; Relation toast_rel; AttrNumber attNums[1]; Oid classObjectId[1]; /* * permissions checking. XXX exactly what is appropriate here? */ #ifndef NO_SECURITY if (!pg_ownercheck(UserName, relationName, RELNAME)) elog(ERROR, "ALTER TABLE: permission denied"); #endif /* * lock the pg_class tuple for update */ reltup = SearchSysCacheTuple(RELNAME, PointerGetDatum(relationName), 0, 0, 0); if (!HeapTupleIsValid(reltup)) elog(ERROR, "ALTER TABLE: relation \"%s\" not found", relationName); class_rel = heap_openr(RelationRelationName, RowExclusiveLock); classtuple.t_self = reltup->t_self; switch (heap_mark4update(class_rel, &classtuple, &buffer)) { case HeapTupleSelfUpdated: case HeapTupleMayBeUpdated: break; default: elog(ERROR, "couldn't lock pg_class tuple"); } reltup = heap_copytuple(&classtuple); ReleaseBuffer(buffer); /* * Grab an exclusive lock on the target table, which we will NOT * release until end of transaction. */ rel = heap_openr(relationName, AccessExclusiveLock); myrelid = RelationGetRelid(rel); /* * Check if there are any toastable attributes on the table */ tupdesc = rel->rd_att; att = tupdesc->attrs; for (i = 0; i < tupdesc->natts; i++) { if (att[i]->attstorage != 'p') { has_toastable_attrs = true; break; } } if (!has_toastable_attrs) { if (silent) { heap_close(rel, NoLock); heap_close(class_rel, NoLock); heap_freetuple(reltup); return; } elog(ERROR, "ALTER TABLE: relation \"%s\" has no toastable attributes", relationName); } /* * XXX is the following check sufficient? At least it would * allow to create TOAST tables for views. But why not - someone * can insert into a view, so it shouldn't be impossible to hide * huge data there :-) */ if (((Form_pg_class) GETSTRUCT(reltup))->relkind != RELKIND_RELATION) { elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", relationName); } if (((Form_pg_class) GETSTRUCT(reltup))->reltoastrelid != InvalidOid) { if (silent) { heap_close(rel, NoLock); heap_close(class_rel, NoLock); heap_freetuple(reltup); return; } elog(ERROR, "ALTER TABLE: relation \"%s\" already has a toast table", relationName); } /* * Create the toast table and its index */ sprintf(toast_relname, "pg_toast_%u", myrelid); sprintf(toast_idxname, "pg_toast_%u_idx", myrelid); /* this is pretty painful... need a tuple descriptor */ tupdesc = CreateTemplateTupleDesc(3); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "chunk_id", OIDOID, -1, 0, false); TupleDescInitEntry(tupdesc, (AttrNumber) 2, "chunk_seq", INT4OID, -1, 0, false); TupleDescInitEntry(tupdesc, (AttrNumber) 3, "chunk_data", BYTEAOID, -1, 0, false); /* XXX what if owning relation is temp? need we mark toasttable too? */ /* XXX How do we know? No naming collisions possible because names */ /* are OID based. And toast table disappears when master table */ /* is destroyed. So what is it good for anyway? Jan */ heap_create_with_catalog(toast_relname, tupdesc, RELKIND_TOASTVALUE, false, true); /* make the toast relation visible, else index creation will fail */ CommandCounterIncrement(); /* create index on chunk_id */ attNums[0] = 1; classObjectId[0] = OID_OPS_OID; index_create(toast_relname, toast_idxname, NULL, NULL, BTREE_AM_OID, 1, attNums, classObjectId, (Node *) NULL, false, false, false, true); /* make the index visible in this transaction */ CommandCounterIncrement(); /* * Get the OIDs of the newly created objects */ toast_rel = heap_openr(toast_relname, NoLock); toast_relid = RelationGetRelid(toast_rel); heap_close(toast_rel, NoLock); toast_rel = index_openr(toast_idxname); toast_idxid = RelationGetRelid(toast_rel); index_close(toast_rel); /* * Store the toast table- and index-Oid's in the relation tuple */ ((Form_pg_class) GETSTRUCT(reltup))->reltoastrelid = toast_relid; ((Form_pg_class) GETSTRUCT(reltup))->reltoastidxid = toast_idxid; heap_update(class_rel, &reltup->t_self, reltup, NULL); /* * Keep catalog indices current */ CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, ridescs); CatalogIndexInsert(ridescs, Num_pg_class_indices, class_rel, reltup); CatalogCloseIndices(Num_pg_class_indices, ridescs); heap_freetuple(reltup); /* * Finally update the toast relations pg_class tuple to say * it has an index. */ reltup = SearchSysCacheTuple(RELNAME, PointerGetDatum(toast_relname), 0, 0, 0); if (!HeapTupleIsValid(reltup)) elog(ERROR, "ALTER TABLE: just created toast relation \"%s\" not found", toast_relname); classtuple.t_self = reltup->t_self; switch (heap_mark4update(class_rel, &classtuple, &buffer)) { case HeapTupleSelfUpdated: case HeapTupleMayBeUpdated: break; default: elog(ERROR, "couldn't lock pg_class tuple"); } reltup = heap_copytuple(&classtuple); ReleaseBuffer(buffer); ((Form_pg_class) GETSTRUCT(reltup))->relhasindex = true; heap_update(class_rel, &reltup->t_self, reltup, NULL); CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, ridescs); CatalogIndexInsert(ridescs, Num_pg_class_indices, class_rel, reltup); CatalogCloseIndices(Num_pg_class_indices, ridescs); heap_freetuple(reltup); /* * Close relatons and make changes visible */ heap_close(class_rel, NoLock); heap_close(rel, NoLock); CommandCounterIncrement(); } /* * * LOCK TABLE * */ void LockTableCommand(LockStmt *lockstmt) { Relation rel; int aclresult; rel = heap_openr(lockstmt->relname, NoLock); if (!RelationIsValid(rel)) elog(ERROR, "Relation '%s' does not exist", lockstmt->relname); if (lockstmt->mode == AccessShareLock) aclresult = pg_aclcheck(lockstmt->relname, GetPgUserName(), ACL_RD); else aclresult = pg_aclcheck(lockstmt->relname, GetPgUserName(), ACL_WR); if (aclresult != ACLCHECK_OK) elog(ERROR, "LOCK TABLE: permission denied"); LockRelation(rel, lockstmt->mode); heap_close(rel, NoLock); /* close rel, keep lock */ }