diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index e8b0d8bc013..7bdc238e77c 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -1,14 +1,14 @@ /*------------------------------------------------------------------------- * * tablecmds.c - * Commands for altering table structures and settings + * Commands for creating and altering table structures and settings * * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/tablecmds.c,v 1.9 2002/04/24 02:48:54 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/tablecmds.c,v 1.10 2002/04/26 19:29:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -48,17 +48,16 @@ #include "utils/relcache.h" -static void drop_default(Oid relid, int16 attnum); -static bool needs_toast_table(Relation rel); -static void CheckTupleType(Form_pg_class tuple_class); - +static List *MergeDomainAttributes(List *schema); static List *MergeAttributes(List *schema, List *supers, bool istemp, List **supOids, List **supconstr, bool *supHasOids); static bool change_varattnos_of_a_node(Node *node, const AttrNumber *newattno); static void StoreCatalogInheritance(Oid relationId, List *supers); static int findAttrByName(const char *attributeName, List *schema); static void setRelhassubclassInRelation(Oid relationId, bool relhassubclass); -static List *MergeDomainAttributes(List *schema); +static void drop_default(Oid relid, int16 attnum); +static void CheckTupleType(Form_pg_class tuple_class); +static bool needs_toast_table(Relation rel); /* Used by attribute and relation renaming routines: */ @@ -74,1598 +73,6 @@ static void update_ri_trigger_args(Oid relid, bool update_relname); -/* ---------------- - * 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, heap_getattr, 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(Oid myrelid, - bool inherits, - ColumnDef *colDef) -{ - Relation rel, - pgclass, - attrdesc; - HeapTuple reltup; - HeapTuple newreltup; - HeapTuple attributeTuple; - Form_pg_attribute attribute; - FormData_pg_attribute attributeD; - int i; - int minattnum, - maxatts; - HeapTuple typeTuple; - Form_pg_type tform; - int attndims; - - /* - * Grab an exclusive lock on the target table, which we will NOT - * release until end of transaction. - */ - rel = heap_open(myrelid, AccessExclusiveLock); - - if (rel->rd_rel->relkind != RELKIND_RELATION) - elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", - RelationGetRelationName(rel)); - - /* - * 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 - && IsSystemRelation(rel)) - elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog", - RelationGetRelationName(rel)); - if (!pg_class_ownercheck(myrelid, GetUserId())) - elog(ERROR, "ALTER TABLE: \"%s\": permission denied", - RelationGetRelationName(rel)); - - /* - * Recurse to add the column to child classes, if requested. - * - * any permissions or problems with duplicate attributes will cause the - * whole transaction to abort, which is what we want -- all or - * nothing. - */ - 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); - - if (childrelid == myrelid) - continue; - - AlterTableAddColumn(childrelid, false, colDef); - } - } - - /* - * OK, get on with it... - * - * Implementation restrictions: because we don't touch the table rows, - * the new column values will initially appear to be NULLs. (This - * happens because the heap tuple access routines always check for - * attnum > # of attributes in tuple, and return NULL if so.) - * Therefore we can't support a DEFAULT value in SQL92-compliant - * fashion, and we also can't allow a NOT NULL constraint. - * - * We do allow CHECK constraints, even though these theoretically could - * fail for NULL rows (eg, CHECK (newcol IS NOT NULL)). - */ - if (colDef->raw_default || colDef->cooked_default) - elog(ERROR, "Adding columns with defaults is not implemented." - "\n\tAdd the column, then use ALTER TABLE SET DEFAULT."); - - if (colDef->is_not_null) - elog(ERROR, "Adding NOT NULL columns is not implemented." - "\n\tAdd the column, then use ALTER TABLE ... SET NOT NULL."); - - pgclass = heap_openr(RelationRelationName, RowExclusiveLock); - - reltup = SearchSysCache(RELOID, - ObjectIdGetDatum(myrelid), - 0, 0, 0); - if (!HeapTupleIsValid(reltup)) - elog(ERROR, "ALTER TABLE: relation \"%s\" not found", - RelationGetRelationName(rel)); - - if (SearchSysCacheExists(ATTNAME, - ObjectIdGetDatum(myrelid), - PointerGetDatum(colDef->colname), - 0, 0)) - elog(ERROR, "ALTER TABLE: column name \"%s\" already exists in table \"%s\"", - colDef->colname, RelationGetRelationName(rel)); - - minattnum = ((Form_pg_class) GETSTRUCT(reltup))->relnatts; - maxatts = minattnum + 1; - if (maxatts > MaxHeapAttributeNumber) - elog(ERROR, "ALTER TABLE: relations limited to %d columns", - MaxHeapAttributeNumber); - i = minattnum + 1; - - attrdesc = heap_openr(AttributeRelationName, RowExclusiveLock); - - if (colDef->typename->arrayBounds) - attndims = length(colDef->typename->arrayBounds); - else - attndims = 0; - - typeTuple = typenameType(colDef->typename); - tform = (Form_pg_type) GETSTRUCT(typeTuple); - - attributeTuple = heap_addheader(Natts_pg_attribute, - ATTRIBUTE_TUPLE_SIZE, - (void *) &attributeD); - - attribute = (Form_pg_attribute) GETSTRUCT(attributeTuple); - - attribute->attrelid = myrelid; - namestrcpy(&(attribute->attname), colDef->colname); - attribute->atttypid = typeTuple->t_data->t_oid; - attribute->attstattarget = DEFAULT_ATTSTATTARGET; - attribute->attlen = tform->typlen; - attribute->attcacheoff = -1; - attribute->atttypmod = colDef->typename->typmod; - attribute->attnum = i; - attribute->attbyval = tform->typbyval; - attribute->attndims = attndims; - attribute->attisset = (bool) (tform->typtype == 'c'); - attribute->attstorage = tform->typstorage; - attribute->attalign = tform->typalign; - attribute->attnotnull = colDef->is_not_null; - attribute->atthasdef = (colDef->raw_default != NULL || - colDef->cooked_default != NULL); - - ReleaseSysCache(typeTuple); - - heap_insert(attrdesc, attributeTuple); - - /* Update indexes on pg_attribute */ - if (RelationGetForm(attrdesc)->relhasindex) - { - Relation idescs[Num_pg_attr_indices]; - - CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, idescs); - CatalogIndexInsert(idescs, Num_pg_attr_indices, attrdesc, attributeTuple); - CatalogCloseIndices(Num_pg_attr_indices, idescs); - } - - heap_close(attrdesc, RowExclusiveLock); - - /* - * Update number of attributes in pg_class tuple - */ - newreltup = heap_copytuple(reltup); - - ((Form_pg_class) GETSTRUCT(newreltup))->relnatts = maxatts; - simple_heap_update(pgclass, &newreltup->t_self, newreltup); - - /* keep catalog indices current */ - if (RelationGetForm(pgclass)->relhasindex) - { - Relation ridescs[Num_pg_class_indices]; - - CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, ridescs); - CatalogIndexInsert(ridescs, Num_pg_class_indices, pgclass, newreltup); - CatalogCloseIndices(Num_pg_class_indices, ridescs); - } - - heap_freetuple(newreltup); - ReleaseSysCache(reltup); - - heap_close(pgclass, NoLock); - - heap_close(rel, NoLock); /* close rel but keep lock! */ - - /* - * Make our catalog updates visible for subsequent steps. - */ - CommandCounterIncrement(); - - /* - * Add any CHECK constraints attached to the new column. - * - * To do this we must re-open the rel so that its new attr list gets - * loaded into the relcache. - */ - if (colDef->constraints != NIL) - { - rel = heap_open(myrelid, AccessExclusiveLock); - AddRelationRawConstraints(rel, NIL, colDef->constraints); - heap_close(rel, NoLock); - } - - /* - * Automatically create the secondary relation for TOAST if it - * formerly had no such but now has toastable attributes. - */ - AlterTableCreateToastTable(myrelid, true); -} - -/* - * ALTER TABLE ALTER COLUMN DROP NOT NULL - */ -void -AlterTableAlterColumnDropNotNull(Oid myrelid, - bool inh, const char *colName) -{ - Relation rel; - HeapTuple tuple; - AttrNumber attnum; - Relation attr_rel; - List *indexoidlist; - List *indexoidscan; - - rel = heap_open(myrelid, AccessExclusiveLock); - - if (rel->rd_rel->relkind != RELKIND_RELATION) - elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", - RelationGetRelationName(rel)); - - if (!allowSystemTableMods - && IsSystemRelation(rel)) - elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog", - RelationGetRelationName(rel)); - - if (!pg_class_ownercheck(myrelid, GetUserId())) - elog(ERROR, "ALTER TABLE: \"%s\": permission denied", - RelationGetRelationName(rel)); - - /* - * 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; - AlterTableAlterColumnDropNotNull(childrelid, - false, colName); - } - } - - /* -= now do the thing on this relation =- */ - - /* - * get the number of the attribute - */ - tuple = SearchSysCache(ATTNAME, - ObjectIdGetDatum(myrelid), - PointerGetDatum(colName), - 0, 0); - if (!HeapTupleIsValid(tuple)) - elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"", - RelationGetRelationName(rel), colName); - - attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum; - ReleaseSysCache(tuple); - - /* Prevent them from altering a system attribute */ - if (attnum < 0) - elog(ERROR, "ALTER TABLE: Cannot alter system attribute \"%s\"", - colName); - - /* - * Check that the attribute is not in a primary key - */ - - /* Loop over all indices on the relation */ - indexoidlist = RelationGetIndexList(rel); - - foreach(indexoidscan, indexoidlist) - { - Oid indexoid = lfirsti(indexoidscan); - HeapTuple indexTuple; - Form_pg_index indexStruct; - int i; - - indexTuple = SearchSysCache(INDEXRELID, - ObjectIdGetDatum(indexoid), - 0, 0, 0); - if (!HeapTupleIsValid(indexTuple)) - elog(ERROR, "ALTER TABLE: Index %u not found", - indexoid); - indexStruct = (Form_pg_index) GETSTRUCT(indexTuple); - - /* If the index is not a primary key, skip the check */ - if (indexStruct->indisprimary) - { - /* - * Loop over each attribute in the primary key and - * see if it matches the to-be-altered attribute - */ - for (i = 0; i < INDEX_MAX_KEYS && - indexStruct->indkey[i] != InvalidAttrNumber; i++) - { - if (indexStruct->indkey[i] == attnum) - elog(ERROR, "ALTER TABLE: Attribute \"%s\" is in a primary key", colName); - } - } - - ReleaseSysCache(indexTuple); - } - - freeList(indexoidlist); - - /* - * Okay, actually perform the catalog change - */ - attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock); - - tuple = SearchSysCacheCopy(ATTNAME, - ObjectIdGetDatum(myrelid), - PointerGetDatum(colName), - 0, 0); - if (!HeapTupleIsValid(tuple)) /* shouldn't happen */ - elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"", - RelationGetRelationName(rel), colName); - - ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = FALSE; - - simple_heap_update(attr_rel, &tuple->t_self, tuple); - - /* keep the system catalog indices current */ - if (RelationGetForm(attr_rel)->relhasindex) - { - Relation idescs[Num_pg_attr_indices]; - - CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, idescs); - CatalogIndexInsert(idescs, Num_pg_attr_indices, attr_rel, tuple); - CatalogCloseIndices(Num_pg_attr_indices, idescs); - } - - heap_close(attr_rel, RowExclusiveLock); - - heap_close(rel, NoLock); -} - -/* - * ALTER TABLE ALTER COLUMN SET NOT NULL - */ -void -AlterTableAlterColumnSetNotNull(Oid myrelid, - bool inh, const char *colName) -{ - Relation rel; - HeapTuple tuple; - AttrNumber attnum; - Relation attr_rel; - HeapScanDesc scan; - TupleDesc tupdesc; - - rel = heap_open(myrelid, AccessExclusiveLock); - - if (rel->rd_rel->relkind != RELKIND_RELATION) - elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", - RelationGetRelationName(rel)); - - if (!allowSystemTableMods - && IsSystemRelation(rel)) - elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog", - RelationGetRelationName(rel)); - - if (!pg_class_ownercheck(myrelid, GetUserId())) - elog(ERROR, "ALTER TABLE: \"%s\": permission denied", - RelationGetRelationName(rel)); - - /* - * 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; - AlterTableAlterColumnSetNotNull(childrelid, - false, colName); - } - } - - /* -= now do the thing on this relation =- */ - - /* - * get the number of the attribute - */ - tuple = SearchSysCache(ATTNAME, - ObjectIdGetDatum(myrelid), - PointerGetDatum(colName), - 0, 0); - if (!HeapTupleIsValid(tuple)) - elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"", - RelationGetRelationName(rel), colName); - - attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum; - ReleaseSysCache(tuple); - - /* Prevent them from altering a system attribute */ - if (attnum < 0) - elog(ERROR, "ALTER TABLE: Cannot alter system attribute \"%s\"", - colName); - - /* - * Perform a scan to ensure that there are no NULL - * values already in the relation - */ - tupdesc = RelationGetDescr(rel); - - scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL); - - while (HeapTupleIsValid(tuple = heap_getnext(scan, 0))) - { - Datum d; - bool isnull; - - d = heap_getattr(tuple, attnum, tupdesc, &isnull); - - if (isnull) - elog(ERROR, "ALTER TABLE: Attribute \"%s\" contains NULL values", - colName); - } - - heap_endscan(scan); - - /* - * Okay, actually perform the catalog change - */ - attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock); - - tuple = SearchSysCacheCopy(ATTNAME, - ObjectIdGetDatum(myrelid), - PointerGetDatum(colName), - 0, 0); - if (!HeapTupleIsValid(tuple)) /* shouldn't happen */ - elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"", - RelationGetRelationName(rel), colName); - - ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = TRUE; - - simple_heap_update(attr_rel, &tuple->t_self, tuple); - - /* keep the system catalog indices current */ - if (RelationGetForm(attr_rel)->relhasindex) - { - Relation idescs[Num_pg_attr_indices]; - - CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, idescs); - CatalogIndexInsert(idescs, Num_pg_attr_indices, attr_rel, tuple); - CatalogCloseIndices(Num_pg_attr_indices, idescs); - } - - heap_close(attr_rel, RowExclusiveLock); - - heap_close(rel, NoLock); -} - - -/* - * ALTER TABLE ALTER COLUMN SET/DROP DEFAULT - */ -void -AlterTableAlterColumnDefault(Oid myrelid, - bool inh, const char *colName, - Node *newDefault) -{ - Relation rel; - HeapTuple tuple; - AttrNumber attnum; - - rel = heap_open(myrelid, AccessExclusiveLock); - - /* - * We allow defaults on views so that INSERT into a view can have - * default-ish behavior. This works because the rewriter substitutes - * default values into INSERTs before it expands rules. - */ - if (rel->rd_rel->relkind != RELKIND_RELATION && - rel->rd_rel->relkind != RELKIND_VIEW) - elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table or view", - RelationGetRelationName(rel)); - - if (!allowSystemTableMods - && IsSystemRelation(rel)) - elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog", - RelationGetRelationName(rel)); - - if (!pg_class_ownercheck(myrelid, GetUserId())) - elog(ERROR, "ALTER TABLE: \"%s\": permission denied", - RelationGetRelationName(rel)); - - /* - * 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; - AlterTableAlterColumnDefault(childrelid, - false, colName, newDefault); - } - } - - /* -= now do the thing on this relation =- */ - - /* - * get the number of the attribute - */ - tuple = SearchSysCache(ATTNAME, - ObjectIdGetDatum(myrelid), - PointerGetDatum(colName), - 0, 0); - if (!HeapTupleIsValid(tuple)) - elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"", - RelationGetRelationName(rel), colName); - - attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum; - ReleaseSysCache(tuple); - - if (newDefault) - { - /* SET DEFAULT */ - 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; - - /* - * This function is intended for CREATE TABLE, so it processes a - * _list_ of defaults, but we just do one. - */ - AddRelationRawConstraints(rel, makeList1(rawEnt), NIL); - } - else - { - /* DROP DEFAULT */ - Relation attr_rel; - - /* Fix the pg_attribute row */ - attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock); - - tuple = SearchSysCacheCopy(ATTNAME, - ObjectIdGetDatum(myrelid), - PointerGetDatum(colName), - 0, 0); - if (!HeapTupleIsValid(tuple)) /* shouldn't happen */ - elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"", - RelationGetRelationName(rel), colName); - - ((Form_pg_attribute) GETSTRUCT(tuple))->atthasdef = FALSE; - - simple_heap_update(attr_rel, &tuple->t_self, tuple); - - /* keep the system catalog indices current */ - if (RelationGetForm(attr_rel)->relhasindex) - { - Relation idescs[Num_pg_attr_indices]; - - CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, idescs); - CatalogIndexInsert(idescs, Num_pg_attr_indices, attr_rel, tuple); - CatalogCloseIndices(Num_pg_attr_indices, idescs); - } - - heap_close(attr_rel, RowExclusiveLock); - - /* get rid of actual default definition in pg_attrdef */ - drop_default(myrelid, attnum); - } - - 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, RowExclusiveLock); - 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); - - if (HeapTupleIsValid(tuple = heap_getnext(scan, 0))) - simple_heap_delete(attrdef_rel, &tuple->t_self); - - heap_endscan(scan); - - heap_close(attrdef_rel, NoLock); -} - - -/* - * ALTER TABLE ALTER COLUMN SET STATISTICS / STORAGE - */ -void -AlterTableAlterColumnFlags(Oid myrelid, - bool inh, const char *colName, - Node *flagValue, const char *flagType) -{ - Relation rel; - int newtarget = 1; - char newstorage = 'p'; - Relation attrelation; - HeapTuple tuple; - Form_pg_attribute attrtuple; - - rel = heap_open(myrelid, AccessExclusiveLock); - - if (rel->rd_rel->relkind != RELKIND_RELATION) - elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", - RelationGetRelationName(rel)); - - /* - * we allow statistics case for system tables - */ - if (*flagType != 'S' && !allowSystemTableMods && IsSystemRelation(rel)) - elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog", - RelationGetRelationName(rel)); - - if (!pg_class_ownercheck(myrelid, GetUserId())) - elog(ERROR, "ALTER TABLE: \"%s\": permission denied", - RelationGetRelationName(rel)); - - /* - * Check the supplied parameters before anything else - */ - if (*flagType == 'S') - { - /* STATISTICS */ - Assert(IsA(flagValue, Integer)); - newtarget = intVal(flagValue); - - /* - * Limit target to sane range (should we raise an error instead?) - */ - if (newtarget < 0) - newtarget = 0; - else if (newtarget > 1000) - newtarget = 1000; - } - else if (*flagType == 'M') - { - /* STORAGE */ - char *storagemode; - - Assert(IsA(flagValue, String)); - storagemode = strVal(flagValue); - - if (strcasecmp(storagemode, "plain") == 0) - newstorage = 'p'; - else if (strcasecmp(storagemode, "external") == 0) - newstorage = 'e'; - else if (strcasecmp(storagemode, "extended") == 0) - newstorage = 'x'; - else if (strcasecmp(storagemode, "main") == 0) - newstorage = 'm'; - else - elog(ERROR, "ALTER TABLE: \"%s\" storage not recognized", - storagemode); - } - else - { - elog(ERROR, "ALTER TABLE: Invalid column flag: %c", - (int) *flagType); - } - - /* - * 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; - AlterTableAlterColumnFlags(childrelid, - false, colName, flagValue, flagType); - } - } - - /* -= now do the thing on this relation =- */ - - attrelation = heap_openr(AttributeRelationName, RowExclusiveLock); - - tuple = SearchSysCacheCopy(ATTNAME, - ObjectIdGetDatum(myrelid), - PointerGetDatum(colName), - 0, 0); - if (!HeapTupleIsValid(tuple)) - elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"", - RelationGetRelationName(rel), colName); - attrtuple = (Form_pg_attribute) GETSTRUCT(tuple); - - if (attrtuple->attnum < 0) - elog(ERROR, "ALTER TABLE: cannot change system attribute \"%s\"", - colName); - /* - * Now change the appropriate field - */ - if (*flagType == 'S') - attrtuple->attstattarget = newtarget; - else if (*flagType == 'M') - { - /* - * safety check: do not allow toasted storage modes unless column - * datatype is TOAST-aware. - */ - if (newstorage == 'p' || TypeIsToastable(attrtuple->atttypid)) - attrtuple->attstorage = newstorage; - else - elog(ERROR, "ALTER TABLE: Column datatype %s can only have storage \"plain\"", - format_type_be(attrtuple->atttypid)); - } - - simple_heap_update(attrelation, &tuple->t_self, tuple); - - /* keep system catalog indices current */ - { - Relation irelations[Num_pg_attr_indices]; - - CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, irelations); - CatalogIndexInsert(irelations, Num_pg_attr_indices, attrelation, tuple); - CatalogCloseIndices(Num_pg_attr_indices, irelations); - } - - heap_freetuple(tuple); - heap_close(attrelation, NoLock); - heap_close(rel, NoLock); /* close rel, but keep lock! */ -} - - - -/* - * ALTER TABLE DROP COLUMN - */ -void -AlterTableDropColumn(Oid myrelid, - bool inh, const char *colName, - int behavior) -{ - elog(ERROR, "ALTER TABLE / DROP COLUMN is not implemented"); -} - - - -/* - * ALTER TABLE ADD CONSTRAINT - */ -void -AlterTableAddConstraint(Oid myrelid, - bool inh, List *newConstraints) -{ - Relation rel; - List *listptr; - - /* - * Grab an exclusive lock on the target table, which we will NOT - * release until end of transaction. - */ - rel = heap_open(myrelid, AccessExclusiveLock); - - if (rel->rd_rel->relkind != RELKIND_RELATION) - elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", - RelationGetRelationName(rel)); - - if (!allowSystemTableMods - && IsSystemRelation(rel)) - elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog", - RelationGetRelationName(rel)); - - if (!pg_class_ownercheck(myrelid, GetUserId())) - elog(ERROR, "ALTER TABLE: \"%s\": permission denied", - RelationGetRelationName(rel)); - - 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; - AlterTableAddConstraint(childrelid, false, newConstraints); - } - } - - foreach(listptr, newConstraints) - { - Node *newConstraint = lfirst(listptr); - - switch (nodeTag(newConstraint)) - { - case T_Constraint: - { - Constraint *constr = (Constraint *) newConstraint; - - /* - * Currently, we only expect to see CONSTR_CHECK nodes - * arriving here (see the preprocessing done in - * parser/analyze.c). Use a switch anyway to make it - * easier to add more code later. - */ - switch (constr->contype) - { - case CONSTR_CHECK: - { - ParseState *pstate; - bool successful = true; - HeapScanDesc scan; - ExprContext *econtext; - TupleTableSlot *slot; - HeapTuple tuple; - RangeTblEntry *rte; - List *qual; - Node *expr; - char *name; - - if (constr->name) - name = constr->name; - else - name = ""; - - /* - * We need to make a parse state and range - * table to allow us to transformExpr and - * fix_opids to get a version of the - * expression we can pass to ExecQual - */ - pstate = make_parsestate(NULL); - rte = addRangeTableEntryForRelation(pstate, - myrelid, - makeAlias(RelationGetRelationName(rel), NIL), - false, - true); - addRTEtoQuery(pstate, rte, true, true); - - /* - * Convert the A_EXPR in raw_expr into an - * EXPR - */ - expr = transformExpr(pstate, constr->raw_expr); - - /* - * Make sure it yields a boolean result. - */ - if (exprType(expr) != BOOLOID) - elog(ERROR, "CHECK '%s' does not yield boolean result", - name); - - /* - * Make sure no outside relations are - * referred to. - */ - if (length(pstate->p_rtable) != 1) - elog(ERROR, "Only relation '%s' can be referenced in CHECK", - RelationGetRelationName(rel)); - - /* - * Might as well try to reduce any - * constant expressions. - */ - expr = eval_const_expressions(expr); - - /* And fix the opids */ - fix_opids(expr); - - qual = makeList1(expr); - - /* Make tuple slot to hold tuples */ - slot = MakeTupleTableSlot(); - ExecSetSlotDescriptor(slot, RelationGetDescr(rel), false); - /* Make an expression context for ExecQual */ - econtext = MakeExprContext(slot, CurrentMemoryContext); - - /* - * Scan through the rows now, checking the - * expression at each row. - */ - scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL); - - while (HeapTupleIsValid(tuple = heap_getnext(scan, 0))) - { - ExecStoreTuple(tuple, slot, InvalidBuffer, false); - if (!ExecQual(qual, econtext, true)) - { - successful = false; - break; - } - ResetExprContext(econtext); - } - - heap_endscan(scan); - - FreeExprContext(econtext); - pfree(slot); - - if (!successful) - elog(ERROR, "AlterTableAddConstraint: rejected due to CHECK constraint %s", name); - - /* - * Call AddRelationRawConstraints to do - * the real adding -- It duplicates some - * of the above, but does not check the - * validity of the constraint against - * tuples already in the table. - */ - AddRelationRawConstraints(rel, NIL, - makeList1(constr)); - - break; - } - default: - elog(ERROR, "ALTER TABLE / ADD CONSTRAINT is not implemented for that constraint type."); - } - break; - } - case T_FkConstraint: - { - FkConstraint *fkconstraint = (FkConstraint *) newConstraint; - Relation pkrel; - 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. - * - * XXX wouldn't a lesser lock be sufficient? - */ - pkrel = heap_openrv(fkconstraint->pktable, - AccessExclusiveLock); - - /* - * Validity checks - */ - if (pkrel->rd_rel->relkind != RELKIND_RELATION) - elog(ERROR, "referenced table \"%s\" not a relation", - fkconstraint->pktable->relname); - - if (isTempNamespace(RelationGetNamespace(pkrel)) && - !isTempNamespace(RelationGetNamespace(rel))) - elog(ERROR, "ALTER TABLE / ADD CONSTRAINT: Unable to reference temporary table from permanent table constraint."); - - /* - * First we check for limited correctness of the - * constraint. - * - * NOTE: we assume parser has already checked for - * existence of an appropriate unique index on the - * referenced relation, and that the column datatypes - * are comparable. - * - * Scan through each tuple, calling RI_FKey_check_ins - * (insert trigger) as if that tuple had just been - * inserted. If any of those fail, it should - * elog(ERROR) and that's that. - */ - MemSet(&trig, 0, sizeof(trig)); - trig.tgoid = InvalidOid; - if (fkconstraint->constr_name) - trig.tgname = fkconstraint->constr_name; - else - trig.tgname = ""; - trig.tgenabled = TRUE; - trig.tgisconstraint = TRUE; - trig.tgconstrrelid = RelationGetRelid(pkrel); - trig.tgdeferrable = FALSE; - trig.tginitdeferred = FALSE; - - trig.tgargs = (char **) palloc( - sizeof(char *) * (4 + length(fkconstraint->fk_attrs) - + length(fkconstraint->pk_attrs))); - - trig.tgargs[0] = trig.tgname; - trig.tgargs[1] = RelationGetRelationName(rel); - trig.tgargs[2] = RelationGetRelationName(pkrel); - trig.tgargs[3] = fkconstraint->match_type; - count = 4; - foreach(list, fkconstraint->fk_attrs) - { - Ident *fk_at = lfirst(list); - - trig.tgargs[count] = fk_at->name; - count += 2; - } - count = 5; - foreach(list, fkconstraint->pk_attrs) - { - Ident *pk_at = lfirst(list); - - trig.tgargs[count] = pk_at->name; - count += 2; - } - trig.tgnargs = count - 1; - - scan = heap_beginscan(rel, false, SnapshotNow, 0, 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); - - pfree(trig.tgargs); - - heap_close(pkrel, NoLock); - - break; - } - default: - elog(ERROR, "ALTER TABLE / ADD CONSTRAINT unable to determine type of constraint passed"); - } - } - - /* Close rel, but keep lock till commit */ - heap_close(rel, NoLock); -} - - - -/* - * ALTER TABLE DROP CONSTRAINT - * Note: It is legal to remove a constraint with name "" as it is possible - * to add a constraint with name "". - * Christopher Kings-Lynne - */ -void -AlterTableDropConstraint(Oid myrelid, - bool inh, const char *constrName, - int behavior) -{ - Relation rel; - int deleted; - - /* - * We don't support CASCADE yet - in fact, RESTRICT doesn't work to - * the spec either! - */ - if (behavior == CASCADE) - elog(ERROR, "ALTER TABLE / DROP CONSTRAINT does not support the CASCADE keyword"); - - /* - * Acquire an exclusive lock on the target relation for the duration - * of the operation. - */ - rel = heap_open(myrelid, AccessExclusiveLock); - - /* Disallow DROP CONSTRAINT on views, indexes, sequences, etc */ - if (rel->rd_rel->relkind != RELKIND_RELATION) - elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", - RelationGetRelationName(rel)); - - if (!allowSystemTableMods - && IsSystemRelation(rel)) - elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog", - RelationGetRelationName(rel)); - - if (!pg_class_ownercheck(myrelid, GetUserId())) - elog(ERROR, "ALTER TABLE: \"%s\": permission denied", - RelationGetRelationName(rel)); - - /* - * Since all we have is the name of the constraint, we have to look - * through all catalogs that could possibly contain a constraint for - * this relation. We also keep a count of the number of constraints - * removed. - */ - - deleted = 0; - - /* - * First, we remove all CHECK constraints with the given name - */ - - deleted += RemoveCheckConstraint(rel, constrName, inh); - - /* - * Now we remove NULL, UNIQUE, PRIMARY KEY and FOREIGN KEY - * constraints. - * - * Unimplemented. - */ - - /* Close the target relation */ - heap_close(rel, NoLock); - - /* If zero constraints deleted, complain */ - if (deleted == 0) - elog(ERROR, "ALTER TABLE / DROP CONSTRAINT: %s does not exist", - constrName); - /* Otherwise if more than one constraint deleted, notify */ - else if (deleted > 1) - elog(NOTICE, "Multiple constraints dropped"); -} - -/* - * ALTER TABLE OWNER - */ -void -AlterTableOwner(Oid relationOid, int32 newOwnerSysId) -{ - Relation target_rel; - Relation class_rel; - HeapTuple tuple; - Relation idescs[Num_pg_class_indices]; - Form_pg_class tuple_class; - - /* Get exclusive lock till end of transaction on the target table */ - target_rel = heap_open(relationOid, AccessExclusiveLock); - - /* Get its pg_class tuple, too */ - class_rel = heap_openr(RelationRelationName, RowExclusiveLock); - - tuple = SearchSysCacheCopy(RELOID, - ObjectIdGetDatum(relationOid), - 0, 0, 0); - if (!HeapTupleIsValid(tuple)) - elog(ERROR, "ALTER TABLE: relation %u not found", relationOid); - tuple_class = (Form_pg_class) GETSTRUCT(tuple); - - /* Can we change the ownership of this tuple? */ - CheckTupleType(tuple_class); - - /* - * Okay, this is a valid tuple: change its ownership and - * write to the heap. - */ - tuple_class->relowner = newOwnerSysId; - simple_heap_update(class_rel, &tuple->t_self, tuple); - - /* Keep the catalog indices up to date */ - CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, idescs); - CatalogIndexInsert(idescs, Num_pg_class_indices, class_rel, tuple); - CatalogCloseIndices(Num_pg_class_indices, idescs); - - /* - * If we are operating on a table, also change the ownership of any - * indexes that belong to the table, as well as the table's toast - * table (if it has one) - */ - if (tuple_class->relkind == RELKIND_RELATION || - tuple_class->relkind == RELKIND_TOASTVALUE) - { - List *index_oid_list, *i; - - /* Find all the indexes belonging to this relation */ - index_oid_list = RelationGetIndexList(target_rel); - - /* For each index, recursively change its ownership */ - foreach(i, index_oid_list) - { - AlterTableOwner(lfirsti(i), newOwnerSysId); - } - - freeList(index_oid_list); - } - - if (tuple_class->relkind == RELKIND_RELATION) - { - /* If it has a toast table, recurse to change its ownership */ - if (tuple_class->reltoastrelid != InvalidOid) - { - AlterTableOwner(tuple_class->reltoastrelid, newOwnerSysId); - } - } - - heap_freetuple(tuple); - heap_close(class_rel, RowExclusiveLock); - heap_close(target_rel, NoLock); -} - -static void -CheckTupleType(Form_pg_class tuple_class) -{ - switch (tuple_class->relkind) - { - case RELKIND_RELATION: - case RELKIND_INDEX: - case RELKIND_VIEW: - case RELKIND_SEQUENCE: - case RELKIND_TOASTVALUE: - /* ok to change owner */ - break; - default: - elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table, TOAST table, index, view, or sequence", - NameStr(tuple_class->relname)); - } -} - -/* - * ALTER TABLE CREATE TOAST TABLE - */ -void -AlterTableCreateToastTable(Oid relOid, bool silent) -{ - Relation rel; - HeapTuple reltup; - HeapTupleData classtuple; - TupleDesc tupdesc; - Relation class_rel; - Buffer buffer; - Relation ridescs[Num_pg_class_indices]; - Oid toast_relid; - Oid toast_idxid; - char toast_relname[NAMEDATALEN]; - char toast_idxname[NAMEDATALEN]; - IndexInfo *indexInfo; - Oid classObjectId[2]; - - /* - * Grab an exclusive lock on the target table, which we will NOT - * release until end of transaction. - */ - rel = heap_open(relOid, AccessExclusiveLock); - - if (rel->rd_rel->relkind != RELKIND_RELATION) - elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", - RelationGetRelationName(rel)); - - if (!pg_class_ownercheck(relOid, GetUserId())) - elog(ERROR, "ALTER TABLE: \"%s\": permission denied", - RelationGetRelationName(rel)); - - /* - * lock the pg_class tuple for update (is that really needed?) - */ - class_rel = heap_openr(RelationRelationName, RowExclusiveLock); - - reltup = SearchSysCache(RELOID, - ObjectIdGetDatum(relOid), - 0, 0, 0); - if (!HeapTupleIsValid(reltup)) - elog(ERROR, "ALTER TABLE: relation \"%s\" not found", - RelationGetRelationName(rel)); - classtuple.t_self = reltup->t_self; - ReleaseSysCache(reltup); - - 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); - - /* - * Is it already toasted? - */ - 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", - RelationGetRelationName(rel)); - } - - /* - * Check to see whether the table actually needs a TOAST table. - */ - if (!needs_toast_table(rel)) - { - if (silent) - { - heap_close(rel, NoLock); - heap_close(class_rel, NoLock); - heap_freetuple(reltup); - return; - } - - elog(ERROR, "ALTER TABLE: relation \"%s\" does not need a toast table", - RelationGetRelationName(rel)); - } - - /* - * Create the toast table and its index - */ - sprintf(toast_relname, "pg_toast_%u", relOid); - sprintf(toast_idxname, "pg_toast_%u_index", relOid); - - /* 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); - - /* - * Ensure that the toast table doesn't itself get toasted, or we'll be - * toast :-(. This is essential for chunk_data because type bytea is - * toastable; hit the other two just to be sure. - */ - tupdesc->attrs[0]->attstorage = 'p'; - tupdesc->attrs[1]->attstorage = 'p'; - tupdesc->attrs[2]->attstorage = 'p'; - - /* - * Note: the toast relation is placed in the regular pg_toast namespace - * even if its master relation is a temp table. There cannot be any - * naming collision, and the toast rel will be destroyed when its master - * is, so there's no need to handle the toast rel as temp. - */ - toast_relid = heap_create_with_catalog(toast_relname, - PG_TOAST_NAMESPACE, - tupdesc, - RELKIND_TOASTVALUE, - false, - true); - - /* make the toast relation visible, else index creation will fail */ - CommandCounterIncrement(); - - /* - * Create unique index on chunk_id, chunk_seq. - * - * NOTE: the tuple toaster could actually function with a single-column - * index on chunk_id only. However, it couldn't be unique then. We - * want it to be unique as a check against the possibility of - * duplicate TOAST chunk OIDs. Too, the index might be a little more - * efficient this way, since btree isn't all that happy with large - * numbers of equal keys. - */ - - indexInfo = makeNode(IndexInfo); - indexInfo->ii_NumIndexAttrs = 2; - indexInfo->ii_NumKeyAttrs = 2; - indexInfo->ii_KeyAttrNumbers[0] = 1; - indexInfo->ii_KeyAttrNumbers[1] = 2; - indexInfo->ii_Predicate = NIL; - indexInfo->ii_FuncOid = InvalidOid; - indexInfo->ii_Unique = true; - - classObjectId[0] = OID_BTREE_OPS_OID; - classObjectId[1] = INT4_BTREE_OPS_OID; - - toast_idxid = index_create(toast_relid, toast_idxname, indexInfo, - BTREE_AM_OID, classObjectId, - true, true); - - /* - * Update toast rel's pg_class entry to show that it has an index. The - * index OID is stored into the reltoastidxid field for easy access by - * the tuple toaster. - */ - setRelhasindex(toast_relid, true, true, toast_idxid); - - /* - * Store the toast table's OID in the parent relation's tuple - */ - ((Form_pg_class) GETSTRUCT(reltup))->reltoastrelid = toast_relid; - simple_heap_update(class_rel, &reltup->t_self, reltup); - - /* - * 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); - - /* - * Close relations and make changes visible - */ - heap_close(class_rel, NoLock); - heap_close(rel, NoLock); - - CommandCounterIncrement(); -} - -/* - * Check to see whether the table needs a TOAST table. It does only if - * (1) there are any toastable attributes, and (2) the maximum length - * of a tuple could exceed TOAST_TUPLE_THRESHOLD. (We don't want to - * create a toast table for something like "f1 varchar(20)".) - */ -static bool -needs_toast_table(Relation rel) -{ - int32 data_length = 0; - bool maxlength_unknown = false; - bool has_toastable_attrs = false; - TupleDesc tupdesc; - Form_pg_attribute *att; - int32 tuple_length; - int i; - - tupdesc = rel->rd_att; - att = tupdesc->attrs; - - for (i = 0; i < tupdesc->natts; i++) - { - data_length = att_align(data_length, att[i]->attlen, att[i]->attalign); - if (att[i]->attlen >= 0) - { - /* Fixed-length types are never toastable */ - data_length += att[i]->attlen; - } - else - { - int32 maxlen = type_maximum_size(att[i]->atttypid, - att[i]->atttypmod); - - if (maxlen < 0) - maxlength_unknown = true; - else - data_length += maxlen; - if (att[i]->attstorage != 'p') - has_toastable_attrs = true; - } - } - if (!has_toastable_attrs) - return false; /* nothing to toast? */ - if (maxlength_unknown) - return true; /* any unlimited-length attrs? */ - tuple_length = MAXALIGN(offsetof(HeapTupleHeaderData, t_bits) + - BITMAPLEN(tupdesc->natts)) + - MAXALIGN(data_length); - return (tuple_length > TOAST_TUPLE_THRESHOLD); -} - - /* ---------------------------------------------------------------- * DefineRelation * Creates a new relation. @@ -2846,122 +1253,6 @@ renamerel(Oid relid, const char *newrelname) relation_close(targetrelation, NoLock); } -/* - * renametrig - changes the name of a trigger on a relation - * - * trigger name is changed in trigger catalog. - * No record of the previous name is kept. - * - * get proper relrelation from relation catalog (if not arg) - * scan trigger catalog - * for name conflict (within rel) - * for original trigger (if not arg) - * modify tgname in trigger tuple - * insert modified trigger in trigger catalog - * delete original trigger from trigger catalog - */ -extern void renametrig(Oid relid, - const char *oldname, - const char *newname) -{ - Relation targetrel; - Relation tgrel; - HeapTuple tuple; - SysScanDesc tgscan; - ScanKeyData key; - bool found = FALSE; - Relation idescs[Num_pg_trigger_indices]; - - /* - * Grab an exclusive lock on the target table, which we will NOT - * release until end of transaction. - */ - targetrel = heap_open(relid, AccessExclusiveLock); - - /* - * Scan pg_trigger twice for existing triggers on relation. We do this in - * order to ensure a trigger does not exist with newname (The unique index - * on tgrelid/tgname would complain anyway) and to ensure a trigger does - * exist with oldname. - * - * NOTE that this is cool only because we have AccessExclusiveLock on the - * relation, so the trigger set won't be changing underneath us. - */ - tgrel = heap_openr(TriggerRelationName, RowExclusiveLock); - - /* - * First pass -- look for name conflict - */ - ScanKeyEntryInitialize(&key, 0, - Anum_pg_trigger_tgrelid, - F_OIDEQ, - ObjectIdGetDatum(relid)); - tgscan = systable_beginscan(tgrel, TriggerRelidNameIndex, true, - SnapshotNow, 1, &key); - while (HeapTupleIsValid(tuple = systable_getnext(tgscan))) - { - Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple); - - if (namestrcmp(&(pg_trigger->tgname), newname) == 0) - elog(ERROR, "renametrig: trigger %s already defined on relation %s", - newname, RelationGetRelationName(targetrel)); - } - systable_endscan(tgscan); - - /* - * Second pass -- look for trigger existing with oldname and update - */ - ScanKeyEntryInitialize(&key, 0, - Anum_pg_trigger_tgrelid, - F_OIDEQ, - ObjectIdGetDatum(relid)); - tgscan = systable_beginscan(tgrel, TriggerRelidNameIndex, true, - SnapshotNow, 1, &key); - while (HeapTupleIsValid(tuple = systable_getnext(tgscan))) - { - Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple); - - if (namestrcmp(&(pg_trigger->tgname), oldname) == 0) - { - /* - * Update pg_trigger tuple with new tgname. - * (Scribbling on tuple is OK because it's a copy...) - */ - namestrcpy(&(pg_trigger->tgname), newname); - simple_heap_update(tgrel, &tuple->t_self, tuple); - - /* - * keep system catalog indices current - */ - CatalogOpenIndices(Num_pg_trigger_indices, Name_pg_trigger_indices, idescs); - CatalogIndexInsert(idescs, Num_pg_trigger_indices, tgrel, tuple); - CatalogCloseIndices(Num_pg_trigger_indices, idescs); - - /* - * Invalidate relation's relcache entry so that other - * backends (and this one too!) are sent SI message to make them - * rebuild relcache entries. - */ - CacheInvalidateRelcache(relid); - - found = TRUE; - break; - } - } - systable_endscan(tgscan); - - heap_close(tgrel, RowExclusiveLock); - - if (!found) - elog(ERROR, "renametrig: trigger %s not defined on relation %s", - oldname, RelationGetRelationName(targetrel)); - - /* - * Close rel, but keep exclusive lock! - */ - heap_close(targetrel, NoLock); -} - /* * Given a trigger function OID, determine whether it is an RI trigger, @@ -3193,3 +1484,1592 @@ update_ri_trigger_args(Oid relid, */ CommandCounterIncrement(); } + + +/* ---------------- + * 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, heap_getattr, 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(Oid myrelid, + bool inherits, + ColumnDef *colDef) +{ + Relation rel, + pgclass, + attrdesc; + HeapTuple reltup; + HeapTuple newreltup; + HeapTuple attributeTuple; + Form_pg_attribute attribute; + FormData_pg_attribute attributeD; + int i; + int minattnum, + maxatts; + HeapTuple typeTuple; + Form_pg_type tform; + int attndims; + + /* + * Grab an exclusive lock on the target table, which we will NOT + * release until end of transaction. + */ + rel = heap_open(myrelid, AccessExclusiveLock); + + if (rel->rd_rel->relkind != RELKIND_RELATION) + elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", + RelationGetRelationName(rel)); + + /* + * 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 + && IsSystemRelation(rel)) + elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog", + RelationGetRelationName(rel)); + if (!pg_class_ownercheck(myrelid, GetUserId())) + elog(ERROR, "ALTER TABLE: \"%s\": permission denied", + RelationGetRelationName(rel)); + + /* + * Recurse to add the column to child classes, if requested. + * + * any permissions or problems with duplicate attributes will cause the + * whole transaction to abort, which is what we want -- all or + * nothing. + */ + 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); + + if (childrelid == myrelid) + continue; + + AlterTableAddColumn(childrelid, false, colDef); + } + } + + /* + * OK, get on with it... + * + * Implementation restrictions: because we don't touch the table rows, + * the new column values will initially appear to be NULLs. (This + * happens because the heap tuple access routines always check for + * attnum > # of attributes in tuple, and return NULL if so.) + * Therefore we can't support a DEFAULT value in SQL92-compliant + * fashion, and we also can't allow a NOT NULL constraint. + * + * We do allow CHECK constraints, even though these theoretically could + * fail for NULL rows (eg, CHECK (newcol IS NOT NULL)). + */ + if (colDef->raw_default || colDef->cooked_default) + elog(ERROR, "Adding columns with defaults is not implemented." + "\n\tAdd the column, then use ALTER TABLE SET DEFAULT."); + + if (colDef->is_not_null) + elog(ERROR, "Adding NOT NULL columns is not implemented." + "\n\tAdd the column, then use ALTER TABLE ... SET NOT NULL."); + + pgclass = heap_openr(RelationRelationName, RowExclusiveLock); + + reltup = SearchSysCache(RELOID, + ObjectIdGetDatum(myrelid), + 0, 0, 0); + if (!HeapTupleIsValid(reltup)) + elog(ERROR, "ALTER TABLE: relation \"%s\" not found", + RelationGetRelationName(rel)); + + if (SearchSysCacheExists(ATTNAME, + ObjectIdGetDatum(myrelid), + PointerGetDatum(colDef->colname), + 0, 0)) + elog(ERROR, "ALTER TABLE: column name \"%s\" already exists in table \"%s\"", + colDef->colname, RelationGetRelationName(rel)); + + minattnum = ((Form_pg_class) GETSTRUCT(reltup))->relnatts; + maxatts = minattnum + 1; + if (maxatts > MaxHeapAttributeNumber) + elog(ERROR, "ALTER TABLE: relations limited to %d columns", + MaxHeapAttributeNumber); + i = minattnum + 1; + + attrdesc = heap_openr(AttributeRelationName, RowExclusiveLock); + + if (colDef->typename->arrayBounds) + attndims = length(colDef->typename->arrayBounds); + else + attndims = 0; + + typeTuple = typenameType(colDef->typename); + tform = (Form_pg_type) GETSTRUCT(typeTuple); + + attributeTuple = heap_addheader(Natts_pg_attribute, + ATTRIBUTE_TUPLE_SIZE, + (void *) &attributeD); + + attribute = (Form_pg_attribute) GETSTRUCT(attributeTuple); + + attribute->attrelid = myrelid; + namestrcpy(&(attribute->attname), colDef->colname); + attribute->atttypid = typeTuple->t_data->t_oid; + attribute->attstattarget = DEFAULT_ATTSTATTARGET; + attribute->attlen = tform->typlen; + attribute->attcacheoff = -1; + attribute->atttypmod = colDef->typename->typmod; + attribute->attnum = i; + attribute->attbyval = tform->typbyval; + attribute->attndims = attndims; + attribute->attisset = (bool) (tform->typtype == 'c'); + attribute->attstorage = tform->typstorage; + attribute->attalign = tform->typalign; + attribute->attnotnull = colDef->is_not_null; + attribute->atthasdef = (colDef->raw_default != NULL || + colDef->cooked_default != NULL); + + ReleaseSysCache(typeTuple); + + heap_insert(attrdesc, attributeTuple); + + /* Update indexes on pg_attribute */ + if (RelationGetForm(attrdesc)->relhasindex) + { + Relation idescs[Num_pg_attr_indices]; + + CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, idescs); + CatalogIndexInsert(idescs, Num_pg_attr_indices, attrdesc, attributeTuple); + CatalogCloseIndices(Num_pg_attr_indices, idescs); + } + + heap_close(attrdesc, RowExclusiveLock); + + /* + * Update number of attributes in pg_class tuple + */ + newreltup = heap_copytuple(reltup); + + ((Form_pg_class) GETSTRUCT(newreltup))->relnatts = maxatts; + simple_heap_update(pgclass, &newreltup->t_self, newreltup); + + /* keep catalog indices current */ + if (RelationGetForm(pgclass)->relhasindex) + { + Relation ridescs[Num_pg_class_indices]; + + CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, ridescs); + CatalogIndexInsert(ridescs, Num_pg_class_indices, pgclass, newreltup); + CatalogCloseIndices(Num_pg_class_indices, ridescs); + } + + heap_freetuple(newreltup); + ReleaseSysCache(reltup); + + heap_close(pgclass, NoLock); + + heap_close(rel, NoLock); /* close rel but keep lock! */ + + /* + * Make our catalog updates visible for subsequent steps. + */ + CommandCounterIncrement(); + + /* + * Add any CHECK constraints attached to the new column. + * + * To do this we must re-open the rel so that its new attr list gets + * loaded into the relcache. + */ + if (colDef->constraints != NIL) + { + rel = heap_open(myrelid, AccessExclusiveLock); + AddRelationRawConstraints(rel, NIL, colDef->constraints); + heap_close(rel, NoLock); + } + + /* + * Automatically create the secondary relation for TOAST if it + * formerly had no such but now has toastable attributes. + */ + AlterTableCreateToastTable(myrelid, true); +} + +/* + * ALTER TABLE ALTER COLUMN DROP NOT NULL + */ +void +AlterTableAlterColumnDropNotNull(Oid myrelid, + bool inh, const char *colName) +{ + Relation rel; + HeapTuple tuple; + AttrNumber attnum; + Relation attr_rel; + List *indexoidlist; + List *indexoidscan; + + rel = heap_open(myrelid, AccessExclusiveLock); + + if (rel->rd_rel->relkind != RELKIND_RELATION) + elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", + RelationGetRelationName(rel)); + + if (!allowSystemTableMods + && IsSystemRelation(rel)) + elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog", + RelationGetRelationName(rel)); + + if (!pg_class_ownercheck(myrelid, GetUserId())) + elog(ERROR, "ALTER TABLE: \"%s\": permission denied", + RelationGetRelationName(rel)); + + /* + * 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; + AlterTableAlterColumnDropNotNull(childrelid, + false, colName); + } + } + + /* -= now do the thing on this relation =- */ + + /* + * get the number of the attribute + */ + tuple = SearchSysCache(ATTNAME, + ObjectIdGetDatum(myrelid), + PointerGetDatum(colName), + 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"", + RelationGetRelationName(rel), colName); + + attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum; + ReleaseSysCache(tuple); + + /* Prevent them from altering a system attribute */ + if (attnum < 0) + elog(ERROR, "ALTER TABLE: Cannot alter system attribute \"%s\"", + colName); + + /* + * Check that the attribute is not in a primary key + */ + + /* Loop over all indices on the relation */ + indexoidlist = RelationGetIndexList(rel); + + foreach(indexoidscan, indexoidlist) + { + Oid indexoid = lfirsti(indexoidscan); + HeapTuple indexTuple; + Form_pg_index indexStruct; + int i; + + indexTuple = SearchSysCache(INDEXRELID, + ObjectIdGetDatum(indexoid), + 0, 0, 0); + if (!HeapTupleIsValid(indexTuple)) + elog(ERROR, "ALTER TABLE: Index %u not found", + indexoid); + indexStruct = (Form_pg_index) GETSTRUCT(indexTuple); + + /* If the index is not a primary key, skip the check */ + if (indexStruct->indisprimary) + { + /* + * Loop over each attribute in the primary key and + * see if it matches the to-be-altered attribute + */ + for (i = 0; i < INDEX_MAX_KEYS && + indexStruct->indkey[i] != InvalidAttrNumber; i++) + { + if (indexStruct->indkey[i] == attnum) + elog(ERROR, "ALTER TABLE: Attribute \"%s\" is in a primary key", colName); + } + } + + ReleaseSysCache(indexTuple); + } + + freeList(indexoidlist); + + /* + * Okay, actually perform the catalog change + */ + attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock); + + tuple = SearchSysCacheCopy(ATTNAME, + ObjectIdGetDatum(myrelid), + PointerGetDatum(colName), + 0, 0); + if (!HeapTupleIsValid(tuple)) /* shouldn't happen */ + elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"", + RelationGetRelationName(rel), colName); + + ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = FALSE; + + simple_heap_update(attr_rel, &tuple->t_self, tuple); + + /* keep the system catalog indices current */ + if (RelationGetForm(attr_rel)->relhasindex) + { + Relation idescs[Num_pg_attr_indices]; + + CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, idescs); + CatalogIndexInsert(idescs, Num_pg_attr_indices, attr_rel, tuple); + CatalogCloseIndices(Num_pg_attr_indices, idescs); + } + + heap_close(attr_rel, RowExclusiveLock); + + heap_close(rel, NoLock); +} + +/* + * ALTER TABLE ALTER COLUMN SET NOT NULL + */ +void +AlterTableAlterColumnSetNotNull(Oid myrelid, + bool inh, const char *colName) +{ + Relation rel; + HeapTuple tuple; + AttrNumber attnum; + Relation attr_rel; + HeapScanDesc scan; + TupleDesc tupdesc; + + rel = heap_open(myrelid, AccessExclusiveLock); + + if (rel->rd_rel->relkind != RELKIND_RELATION) + elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", + RelationGetRelationName(rel)); + + if (!allowSystemTableMods + && IsSystemRelation(rel)) + elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog", + RelationGetRelationName(rel)); + + if (!pg_class_ownercheck(myrelid, GetUserId())) + elog(ERROR, "ALTER TABLE: \"%s\": permission denied", + RelationGetRelationName(rel)); + + /* + * 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; + AlterTableAlterColumnSetNotNull(childrelid, + false, colName); + } + } + + /* -= now do the thing on this relation =- */ + + /* + * get the number of the attribute + */ + tuple = SearchSysCache(ATTNAME, + ObjectIdGetDatum(myrelid), + PointerGetDatum(colName), + 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"", + RelationGetRelationName(rel), colName); + + attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum; + ReleaseSysCache(tuple); + + /* Prevent them from altering a system attribute */ + if (attnum < 0) + elog(ERROR, "ALTER TABLE: Cannot alter system attribute \"%s\"", + colName); + + /* + * Perform a scan to ensure that there are no NULL + * values already in the relation + */ + tupdesc = RelationGetDescr(rel); + + scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL); + + while (HeapTupleIsValid(tuple = heap_getnext(scan, 0))) + { + Datum d; + bool isnull; + + d = heap_getattr(tuple, attnum, tupdesc, &isnull); + + if (isnull) + elog(ERROR, "ALTER TABLE: Attribute \"%s\" contains NULL values", + colName); + } + + heap_endscan(scan); + + /* + * Okay, actually perform the catalog change + */ + attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock); + + tuple = SearchSysCacheCopy(ATTNAME, + ObjectIdGetDatum(myrelid), + PointerGetDatum(colName), + 0, 0); + if (!HeapTupleIsValid(tuple)) /* shouldn't happen */ + elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"", + RelationGetRelationName(rel), colName); + + ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = TRUE; + + simple_heap_update(attr_rel, &tuple->t_self, tuple); + + /* keep the system catalog indices current */ + if (RelationGetForm(attr_rel)->relhasindex) + { + Relation idescs[Num_pg_attr_indices]; + + CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, idescs); + CatalogIndexInsert(idescs, Num_pg_attr_indices, attr_rel, tuple); + CatalogCloseIndices(Num_pg_attr_indices, idescs); + } + + heap_close(attr_rel, RowExclusiveLock); + + heap_close(rel, NoLock); +} + + +/* + * ALTER TABLE ALTER COLUMN SET/DROP DEFAULT + */ +void +AlterTableAlterColumnDefault(Oid myrelid, + bool inh, const char *colName, + Node *newDefault) +{ + Relation rel; + HeapTuple tuple; + AttrNumber attnum; + + rel = heap_open(myrelid, AccessExclusiveLock); + + /* + * We allow defaults on views so that INSERT into a view can have + * default-ish behavior. This works because the rewriter substitutes + * default values into INSERTs before it expands rules. + */ + if (rel->rd_rel->relkind != RELKIND_RELATION && + rel->rd_rel->relkind != RELKIND_VIEW) + elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table or view", + RelationGetRelationName(rel)); + + if (!allowSystemTableMods + && IsSystemRelation(rel)) + elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog", + RelationGetRelationName(rel)); + + if (!pg_class_ownercheck(myrelid, GetUserId())) + elog(ERROR, "ALTER TABLE: \"%s\": permission denied", + RelationGetRelationName(rel)); + + /* + * 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; + AlterTableAlterColumnDefault(childrelid, + false, colName, newDefault); + } + } + + /* -= now do the thing on this relation =- */ + + /* + * get the number of the attribute + */ + tuple = SearchSysCache(ATTNAME, + ObjectIdGetDatum(myrelid), + PointerGetDatum(colName), + 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"", + RelationGetRelationName(rel), colName); + + attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum; + ReleaseSysCache(tuple); + + if (newDefault) + { + /* SET DEFAULT */ + 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; + + /* + * This function is intended for CREATE TABLE, so it processes a + * _list_ of defaults, but we just do one. + */ + AddRelationRawConstraints(rel, makeList1(rawEnt), NIL); + } + else + { + /* DROP DEFAULT */ + Relation attr_rel; + + /* Fix the pg_attribute row */ + attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock); + + tuple = SearchSysCacheCopy(ATTNAME, + ObjectIdGetDatum(myrelid), + PointerGetDatum(colName), + 0, 0); + if (!HeapTupleIsValid(tuple)) /* shouldn't happen */ + elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"", + RelationGetRelationName(rel), colName); + + ((Form_pg_attribute) GETSTRUCT(tuple))->atthasdef = FALSE; + + simple_heap_update(attr_rel, &tuple->t_self, tuple); + + /* keep the system catalog indices current */ + if (RelationGetForm(attr_rel)->relhasindex) + { + Relation idescs[Num_pg_attr_indices]; + + CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, idescs); + CatalogIndexInsert(idescs, Num_pg_attr_indices, attr_rel, tuple); + CatalogCloseIndices(Num_pg_attr_indices, idescs); + } + + heap_close(attr_rel, RowExclusiveLock); + + /* get rid of actual default definition in pg_attrdef */ + drop_default(myrelid, attnum); + } + + 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, RowExclusiveLock); + 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); + + if (HeapTupleIsValid(tuple = heap_getnext(scan, 0))) + simple_heap_delete(attrdef_rel, &tuple->t_self); + + heap_endscan(scan); + + heap_close(attrdef_rel, NoLock); +} + + +/* + * ALTER TABLE ALTER COLUMN SET STATISTICS / STORAGE + */ +void +AlterTableAlterColumnFlags(Oid myrelid, + bool inh, const char *colName, + Node *flagValue, const char *flagType) +{ + Relation rel; + int newtarget = 1; + char newstorage = 'p'; + Relation attrelation; + HeapTuple tuple; + Form_pg_attribute attrtuple; + + rel = heap_open(myrelid, AccessExclusiveLock); + + if (rel->rd_rel->relkind != RELKIND_RELATION) + elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", + RelationGetRelationName(rel)); + + /* + * we allow statistics case for system tables + */ + if (*flagType != 'S' && !allowSystemTableMods && IsSystemRelation(rel)) + elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog", + RelationGetRelationName(rel)); + + if (!pg_class_ownercheck(myrelid, GetUserId())) + elog(ERROR, "ALTER TABLE: \"%s\": permission denied", + RelationGetRelationName(rel)); + + /* + * Check the supplied parameters before anything else + */ + if (*flagType == 'S') + { + /* STATISTICS */ + Assert(IsA(flagValue, Integer)); + newtarget = intVal(flagValue); + + /* + * Limit target to sane range (should we raise an error instead?) + */ + if (newtarget < 0) + newtarget = 0; + else if (newtarget > 1000) + newtarget = 1000; + } + else if (*flagType == 'M') + { + /* STORAGE */ + char *storagemode; + + Assert(IsA(flagValue, String)); + storagemode = strVal(flagValue); + + if (strcasecmp(storagemode, "plain") == 0) + newstorage = 'p'; + else if (strcasecmp(storagemode, "external") == 0) + newstorage = 'e'; + else if (strcasecmp(storagemode, "extended") == 0) + newstorage = 'x'; + else if (strcasecmp(storagemode, "main") == 0) + newstorage = 'm'; + else + elog(ERROR, "ALTER TABLE: \"%s\" storage not recognized", + storagemode); + } + else + { + elog(ERROR, "ALTER TABLE: Invalid column flag: %c", + (int) *flagType); + } + + /* + * 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; + AlterTableAlterColumnFlags(childrelid, + false, colName, flagValue, flagType); + } + } + + /* -= now do the thing on this relation =- */ + + attrelation = heap_openr(AttributeRelationName, RowExclusiveLock); + + tuple = SearchSysCacheCopy(ATTNAME, + ObjectIdGetDatum(myrelid), + PointerGetDatum(colName), + 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"", + RelationGetRelationName(rel), colName); + attrtuple = (Form_pg_attribute) GETSTRUCT(tuple); + + if (attrtuple->attnum < 0) + elog(ERROR, "ALTER TABLE: cannot change system attribute \"%s\"", + colName); + /* + * Now change the appropriate field + */ + if (*flagType == 'S') + attrtuple->attstattarget = newtarget; + else if (*flagType == 'M') + { + /* + * safety check: do not allow toasted storage modes unless column + * datatype is TOAST-aware. + */ + if (newstorage == 'p' || TypeIsToastable(attrtuple->atttypid)) + attrtuple->attstorage = newstorage; + else + elog(ERROR, "ALTER TABLE: Column datatype %s can only have storage \"plain\"", + format_type_be(attrtuple->atttypid)); + } + + simple_heap_update(attrelation, &tuple->t_self, tuple); + + /* keep system catalog indices current */ + { + Relation irelations[Num_pg_attr_indices]; + + CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, irelations); + CatalogIndexInsert(irelations, Num_pg_attr_indices, attrelation, tuple); + CatalogCloseIndices(Num_pg_attr_indices, irelations); + } + + heap_freetuple(tuple); + heap_close(attrelation, NoLock); + heap_close(rel, NoLock); /* close rel, but keep lock! */ +} + + +/* + * ALTER TABLE DROP COLUMN + */ +void +AlterTableDropColumn(Oid myrelid, + bool inh, const char *colName, + int behavior) +{ + elog(ERROR, "ALTER TABLE / DROP COLUMN is not implemented"); +} + + +/* + * ALTER TABLE ADD CONSTRAINT + */ +void +AlterTableAddConstraint(Oid myrelid, + bool inh, List *newConstraints) +{ + Relation rel; + List *listptr; + + /* + * Grab an exclusive lock on the target table, which we will NOT + * release until end of transaction. + */ + rel = heap_open(myrelid, AccessExclusiveLock); + + if (rel->rd_rel->relkind != RELKIND_RELATION) + elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", + RelationGetRelationName(rel)); + + if (!allowSystemTableMods + && IsSystemRelation(rel)) + elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog", + RelationGetRelationName(rel)); + + if (!pg_class_ownercheck(myrelid, GetUserId())) + elog(ERROR, "ALTER TABLE: \"%s\": permission denied", + RelationGetRelationName(rel)); + + 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; + AlterTableAddConstraint(childrelid, false, newConstraints); + } + } + + foreach(listptr, newConstraints) + { + Node *newConstraint = lfirst(listptr); + + switch (nodeTag(newConstraint)) + { + case T_Constraint: + { + Constraint *constr = (Constraint *) newConstraint; + + /* + * Currently, we only expect to see CONSTR_CHECK nodes + * arriving here (see the preprocessing done in + * parser/analyze.c). Use a switch anyway to make it + * easier to add more code later. + */ + switch (constr->contype) + { + case CONSTR_CHECK: + { + ParseState *pstate; + bool successful = true; + HeapScanDesc scan; + ExprContext *econtext; + TupleTableSlot *slot; + HeapTuple tuple; + RangeTblEntry *rte; + List *qual; + Node *expr; + char *name; + + if (constr->name) + name = constr->name; + else + name = ""; + + /* + * We need to make a parse state and range + * table to allow us to transformExpr and + * fix_opids to get a version of the + * expression we can pass to ExecQual + */ + pstate = make_parsestate(NULL); + rte = addRangeTableEntryForRelation(pstate, + myrelid, + makeAlias(RelationGetRelationName(rel), NIL), + false, + true); + addRTEtoQuery(pstate, rte, true, true); + + /* + * Convert the A_EXPR in raw_expr into an + * EXPR + */ + expr = transformExpr(pstate, constr->raw_expr); + + /* + * Make sure it yields a boolean result. + */ + if (exprType(expr) != BOOLOID) + elog(ERROR, "CHECK '%s' does not yield boolean result", + name); + + /* + * Make sure no outside relations are + * referred to. + */ + if (length(pstate->p_rtable) != 1) + elog(ERROR, "Only relation '%s' can be referenced in CHECK", + RelationGetRelationName(rel)); + + /* + * Might as well try to reduce any + * constant expressions. + */ + expr = eval_const_expressions(expr); + + /* And fix the opids */ + fix_opids(expr); + + qual = makeList1(expr); + + /* Make tuple slot to hold tuples */ + slot = MakeTupleTableSlot(); + ExecSetSlotDescriptor(slot, RelationGetDescr(rel), false); + /* Make an expression context for ExecQual */ + econtext = MakeExprContext(slot, CurrentMemoryContext); + + /* + * Scan through the rows now, checking the + * expression at each row. + */ + scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL); + + while (HeapTupleIsValid(tuple = heap_getnext(scan, 0))) + { + ExecStoreTuple(tuple, slot, InvalidBuffer, false); + if (!ExecQual(qual, econtext, true)) + { + successful = false; + break; + } + ResetExprContext(econtext); + } + + heap_endscan(scan); + + FreeExprContext(econtext); + pfree(slot); + + if (!successful) + elog(ERROR, "AlterTableAddConstraint: rejected due to CHECK constraint %s", name); + + /* + * Call AddRelationRawConstraints to do + * the real adding -- It duplicates some + * of the above, but does not check the + * validity of the constraint against + * tuples already in the table. + */ + AddRelationRawConstraints(rel, NIL, + makeList1(constr)); + + break; + } + default: + elog(ERROR, "ALTER TABLE / ADD CONSTRAINT is not implemented for that constraint type."); + } + break; + } + case T_FkConstraint: + { + FkConstraint *fkconstraint = (FkConstraint *) newConstraint; + Relation pkrel; + 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. + * + * XXX wouldn't a lesser lock be sufficient? + */ + pkrel = heap_openrv(fkconstraint->pktable, + AccessExclusiveLock); + + /* + * Validity checks + */ + if (pkrel->rd_rel->relkind != RELKIND_RELATION) + elog(ERROR, "referenced table \"%s\" not a relation", + fkconstraint->pktable->relname); + + if (isTempNamespace(RelationGetNamespace(pkrel)) && + !isTempNamespace(RelationGetNamespace(rel))) + elog(ERROR, "ALTER TABLE / ADD CONSTRAINT: Unable to reference temporary table from permanent table constraint."); + + /* + * First we check for limited correctness of the + * constraint. + * + * NOTE: we assume parser has already checked for + * existence of an appropriate unique index on the + * referenced relation, and that the column datatypes + * are comparable. + * + * Scan through each tuple, calling RI_FKey_check_ins + * (insert trigger) as if that tuple had just been + * inserted. If any of those fail, it should + * elog(ERROR) and that's that. + */ + MemSet(&trig, 0, sizeof(trig)); + trig.tgoid = InvalidOid; + if (fkconstraint->constr_name) + trig.tgname = fkconstraint->constr_name; + else + trig.tgname = ""; + trig.tgenabled = TRUE; + trig.tgisconstraint = TRUE; + trig.tgconstrrelid = RelationGetRelid(pkrel); + trig.tgdeferrable = FALSE; + trig.tginitdeferred = FALSE; + + trig.tgargs = (char **) palloc( + sizeof(char *) * (4 + length(fkconstraint->fk_attrs) + + length(fkconstraint->pk_attrs))); + + trig.tgargs[0] = trig.tgname; + trig.tgargs[1] = RelationGetRelationName(rel); + trig.tgargs[2] = RelationGetRelationName(pkrel); + trig.tgargs[3] = fkconstraint->match_type; + count = 4; + foreach(list, fkconstraint->fk_attrs) + { + Ident *fk_at = lfirst(list); + + trig.tgargs[count] = fk_at->name; + count += 2; + } + count = 5; + foreach(list, fkconstraint->pk_attrs) + { + Ident *pk_at = lfirst(list); + + trig.tgargs[count] = pk_at->name; + count += 2; + } + trig.tgnargs = count - 1; + + scan = heap_beginscan(rel, false, SnapshotNow, 0, 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); + + pfree(trig.tgargs); + + heap_close(pkrel, NoLock); + + break; + } + default: + elog(ERROR, "ALTER TABLE / ADD CONSTRAINT unable to determine type of constraint passed"); + } + } + + /* Close rel, but keep lock till commit */ + heap_close(rel, NoLock); +} + + +/* + * ALTER TABLE DROP CONSTRAINT + * Note: It is legal to remove a constraint with name "" as it is possible + * to add a constraint with name "". + * Christopher Kings-Lynne + */ +void +AlterTableDropConstraint(Oid myrelid, + bool inh, const char *constrName, + int behavior) +{ + Relation rel; + int deleted; + + /* + * We don't support CASCADE yet - in fact, RESTRICT doesn't work to + * the spec either! + */ + if (behavior == CASCADE) + elog(ERROR, "ALTER TABLE / DROP CONSTRAINT does not support the CASCADE keyword"); + + /* + * Acquire an exclusive lock on the target relation for the duration + * of the operation. + */ + rel = heap_open(myrelid, AccessExclusiveLock); + + /* Disallow DROP CONSTRAINT on views, indexes, sequences, etc */ + if (rel->rd_rel->relkind != RELKIND_RELATION) + elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", + RelationGetRelationName(rel)); + + if (!allowSystemTableMods + && IsSystemRelation(rel)) + elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog", + RelationGetRelationName(rel)); + + if (!pg_class_ownercheck(myrelid, GetUserId())) + elog(ERROR, "ALTER TABLE: \"%s\": permission denied", + RelationGetRelationName(rel)); + + /* + * Since all we have is the name of the constraint, we have to look + * through all catalogs that could possibly contain a constraint for + * this relation. We also keep a count of the number of constraints + * removed. + */ + + deleted = 0; + + /* + * First, we remove all CHECK constraints with the given name + */ + + deleted += RemoveCheckConstraint(rel, constrName, inh); + + /* + * Now we remove NULL, UNIQUE, PRIMARY KEY and FOREIGN KEY + * constraints. + * + * Unimplemented. + */ + + /* Close the target relation */ + heap_close(rel, NoLock); + + /* If zero constraints deleted, complain */ + if (deleted == 0) + elog(ERROR, "ALTER TABLE / DROP CONSTRAINT: %s does not exist", + constrName); + /* Otherwise if more than one constraint deleted, notify */ + else if (deleted > 1) + elog(NOTICE, "Multiple constraints dropped"); +} + +/* + * ALTER TABLE OWNER + */ +void +AlterTableOwner(Oid relationOid, int32 newOwnerSysId) +{ + Relation target_rel; + Relation class_rel; + HeapTuple tuple; + Relation idescs[Num_pg_class_indices]; + Form_pg_class tuple_class; + + /* Get exclusive lock till end of transaction on the target table */ + target_rel = heap_open(relationOid, AccessExclusiveLock); + + /* Get its pg_class tuple, too */ + class_rel = heap_openr(RelationRelationName, RowExclusiveLock); + + tuple = SearchSysCacheCopy(RELOID, + ObjectIdGetDatum(relationOid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "ALTER TABLE: relation %u not found", relationOid); + tuple_class = (Form_pg_class) GETSTRUCT(tuple); + + /* Can we change the ownership of this tuple? */ + CheckTupleType(tuple_class); + + /* + * Okay, this is a valid tuple: change its ownership and + * write to the heap. + */ + tuple_class->relowner = newOwnerSysId; + simple_heap_update(class_rel, &tuple->t_self, tuple); + + /* Keep the catalog indices up to date */ + CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, idescs); + CatalogIndexInsert(idescs, Num_pg_class_indices, class_rel, tuple); + CatalogCloseIndices(Num_pg_class_indices, idescs); + + /* + * If we are operating on a table, also change the ownership of any + * indexes that belong to the table, as well as the table's toast + * table (if it has one) + */ + if (tuple_class->relkind == RELKIND_RELATION || + tuple_class->relkind == RELKIND_TOASTVALUE) + { + List *index_oid_list, *i; + + /* Find all the indexes belonging to this relation */ + index_oid_list = RelationGetIndexList(target_rel); + + /* For each index, recursively change its ownership */ + foreach(i, index_oid_list) + { + AlterTableOwner(lfirsti(i), newOwnerSysId); + } + + freeList(index_oid_list); + } + + if (tuple_class->relkind == RELKIND_RELATION) + { + /* If it has a toast table, recurse to change its ownership */ + if (tuple_class->reltoastrelid != InvalidOid) + { + AlterTableOwner(tuple_class->reltoastrelid, newOwnerSysId); + } + } + + heap_freetuple(tuple); + heap_close(class_rel, RowExclusiveLock); + heap_close(target_rel, NoLock); +} + +static void +CheckTupleType(Form_pg_class tuple_class) +{ + switch (tuple_class->relkind) + { + case RELKIND_RELATION: + case RELKIND_INDEX: + case RELKIND_VIEW: + case RELKIND_SEQUENCE: + case RELKIND_TOASTVALUE: + /* ok to change owner */ + break; + default: + elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table, TOAST table, index, view, or sequence", + NameStr(tuple_class->relname)); + } +} + +/* + * ALTER TABLE CREATE TOAST TABLE + */ +void +AlterTableCreateToastTable(Oid relOid, bool silent) +{ + Relation rel; + HeapTuple reltup; + HeapTupleData classtuple; + TupleDesc tupdesc; + Relation class_rel; + Buffer buffer; + Relation ridescs[Num_pg_class_indices]; + Oid toast_relid; + Oid toast_idxid; + char toast_relname[NAMEDATALEN]; + char toast_idxname[NAMEDATALEN]; + IndexInfo *indexInfo; + Oid classObjectId[2]; + + /* + * Grab an exclusive lock on the target table, which we will NOT + * release until end of transaction. + */ + rel = heap_open(relOid, AccessExclusiveLock); + + if (rel->rd_rel->relkind != RELKIND_RELATION) + elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", + RelationGetRelationName(rel)); + + if (!pg_class_ownercheck(relOid, GetUserId())) + elog(ERROR, "ALTER TABLE: \"%s\": permission denied", + RelationGetRelationName(rel)); + + /* + * lock the pg_class tuple for update (is that really needed?) + */ + class_rel = heap_openr(RelationRelationName, RowExclusiveLock); + + reltup = SearchSysCache(RELOID, + ObjectIdGetDatum(relOid), + 0, 0, 0); + if (!HeapTupleIsValid(reltup)) + elog(ERROR, "ALTER TABLE: relation \"%s\" not found", + RelationGetRelationName(rel)); + classtuple.t_self = reltup->t_self; + ReleaseSysCache(reltup); + + 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); + + /* + * Is it already toasted? + */ + 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", + RelationGetRelationName(rel)); + } + + /* + * Check to see whether the table actually needs a TOAST table. + */ + if (!needs_toast_table(rel)) + { + if (silent) + { + heap_close(rel, NoLock); + heap_close(class_rel, NoLock); + heap_freetuple(reltup); + return; + } + + elog(ERROR, "ALTER TABLE: relation \"%s\" does not need a toast table", + RelationGetRelationName(rel)); + } + + /* + * Create the toast table and its index + */ + sprintf(toast_relname, "pg_toast_%u", relOid); + sprintf(toast_idxname, "pg_toast_%u_index", relOid); + + /* 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); + + /* + * Ensure that the toast table doesn't itself get toasted, or we'll be + * toast :-(. This is essential for chunk_data because type bytea is + * toastable; hit the other two just to be sure. + */ + tupdesc->attrs[0]->attstorage = 'p'; + tupdesc->attrs[1]->attstorage = 'p'; + tupdesc->attrs[2]->attstorage = 'p'; + + /* + * Note: the toast relation is placed in the regular pg_toast namespace + * even if its master relation is a temp table. There cannot be any + * naming collision, and the toast rel will be destroyed when its master + * is, so there's no need to handle the toast rel as temp. + */ + toast_relid = heap_create_with_catalog(toast_relname, + PG_TOAST_NAMESPACE, + tupdesc, + RELKIND_TOASTVALUE, + false, + true); + + /* make the toast relation visible, else index creation will fail */ + CommandCounterIncrement(); + + /* + * Create unique index on chunk_id, chunk_seq. + * + * NOTE: the tuple toaster could actually function with a single-column + * index on chunk_id only. However, it couldn't be unique then. We + * want it to be unique as a check against the possibility of + * duplicate TOAST chunk OIDs. Too, the index might be a little more + * efficient this way, since btree isn't all that happy with large + * numbers of equal keys. + */ + + indexInfo = makeNode(IndexInfo); + indexInfo->ii_NumIndexAttrs = 2; + indexInfo->ii_NumKeyAttrs = 2; + indexInfo->ii_KeyAttrNumbers[0] = 1; + indexInfo->ii_KeyAttrNumbers[1] = 2; + indexInfo->ii_Predicate = NIL; + indexInfo->ii_FuncOid = InvalidOid; + indexInfo->ii_Unique = true; + + classObjectId[0] = OID_BTREE_OPS_OID; + classObjectId[1] = INT4_BTREE_OPS_OID; + + toast_idxid = index_create(toast_relid, toast_idxname, indexInfo, + BTREE_AM_OID, classObjectId, + true, true); + + /* + * Update toast rel's pg_class entry to show that it has an index. The + * index OID is stored into the reltoastidxid field for easy access by + * the tuple toaster. + */ + setRelhasindex(toast_relid, true, true, toast_idxid); + + /* + * Store the toast table's OID in the parent relation's tuple + */ + ((Form_pg_class) GETSTRUCT(reltup))->reltoastrelid = toast_relid; + simple_heap_update(class_rel, &reltup->t_self, reltup); + + /* + * 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); + + /* + * Close relations and make changes visible + */ + heap_close(class_rel, NoLock); + heap_close(rel, NoLock); + + CommandCounterIncrement(); +} + +/* + * Check to see whether the table needs a TOAST table. It does only if + * (1) there are any toastable attributes, and (2) the maximum length + * of a tuple could exceed TOAST_TUPLE_THRESHOLD. (We don't want to + * create a toast table for something like "f1 varchar(20)".) + */ +static bool +needs_toast_table(Relation rel) +{ + int32 data_length = 0; + bool maxlength_unknown = false; + bool has_toastable_attrs = false; + TupleDesc tupdesc; + Form_pg_attribute *att; + int32 tuple_length; + int i; + + tupdesc = rel->rd_att; + att = tupdesc->attrs; + + for (i = 0; i < tupdesc->natts; i++) + { + data_length = att_align(data_length, att[i]->attlen, att[i]->attalign); + if (att[i]->attlen >= 0) + { + /* Fixed-length types are never toastable */ + data_length += att[i]->attlen; + } + else + { + int32 maxlen = type_maximum_size(att[i]->atttypid, + att[i]->atttypmod); + + if (maxlen < 0) + maxlength_unknown = true; + else + data_length += maxlen; + if (att[i]->attstorage != 'p') + has_toastable_attrs = true; + } + } + if (!has_toastable_attrs) + return false; /* nothing to toast? */ + if (maxlength_unknown) + return true; /* any unlimited-length attrs? */ + tuple_length = MAXALIGN(offsetof(HeapTupleHeaderData, t_bits) + + BITMAPLEN(tupdesc->natts)) + + MAXALIGN(data_length); + return (tuple_length > TOAST_TUPLE_THRESHOLD); +} diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index c00b4bebac8..b826297a718 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.114 2002/04/19 16:36:08 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.115 2002/04/26 19:29:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -503,6 +503,123 @@ RelationRemoveTriggers(Relation rel) heap_close(tgrel, RowExclusiveLock); } +/* + * renametrig - changes the name of a trigger on a relation + * + * trigger name is changed in trigger catalog. + * No record of the previous name is kept. + * + * get proper relrelation from relation catalog (if not arg) + * scan trigger catalog + * for name conflict (within rel) + * for original trigger (if not arg) + * modify tgname in trigger tuple + * insert modified trigger in trigger catalog + * delete original trigger from trigger catalog + */ +void +renametrig(Oid relid, + const char *oldname, + const char *newname) +{ + Relation targetrel; + Relation tgrel; + HeapTuple tuple; + SysScanDesc tgscan; + ScanKeyData key; + bool found = FALSE; + Relation idescs[Num_pg_trigger_indices]; + + /* + * Grab an exclusive lock on the target table, which we will NOT + * release until end of transaction. + */ + targetrel = heap_open(relid, AccessExclusiveLock); + + /* + * Scan pg_trigger twice for existing triggers on relation. We do this in + * order to ensure a trigger does not exist with newname (The unique index + * on tgrelid/tgname would complain anyway) and to ensure a trigger does + * exist with oldname. + * + * NOTE that this is cool only because we have AccessExclusiveLock on the + * relation, so the trigger set won't be changing underneath us. + */ + tgrel = heap_openr(TriggerRelationName, RowExclusiveLock); + + /* + * First pass -- look for name conflict + */ + ScanKeyEntryInitialize(&key, 0, + Anum_pg_trigger_tgrelid, + F_OIDEQ, + ObjectIdGetDatum(relid)); + tgscan = systable_beginscan(tgrel, TriggerRelidNameIndex, true, + SnapshotNow, 1, &key); + while (HeapTupleIsValid(tuple = systable_getnext(tgscan))) + { + Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple); + + if (namestrcmp(&(pg_trigger->tgname), newname) == 0) + elog(ERROR, "renametrig: trigger %s already defined on relation %s", + newname, RelationGetRelationName(targetrel)); + } + systable_endscan(tgscan); + + /* + * Second pass -- look for trigger existing with oldname and update + */ + ScanKeyEntryInitialize(&key, 0, + Anum_pg_trigger_tgrelid, + F_OIDEQ, + ObjectIdGetDatum(relid)); + tgscan = systable_beginscan(tgrel, TriggerRelidNameIndex, true, + SnapshotNow, 1, &key); + while (HeapTupleIsValid(tuple = systable_getnext(tgscan))) + { + Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple); + + if (namestrcmp(&(pg_trigger->tgname), oldname) == 0) + { + /* + * Update pg_trigger tuple with new tgname. + * (Scribbling on tuple is OK because it's a copy...) + */ + namestrcpy(&(pg_trigger->tgname), newname); + simple_heap_update(tgrel, &tuple->t_self, tuple); + + /* + * keep system catalog indices current + */ + CatalogOpenIndices(Num_pg_trigger_indices, Name_pg_trigger_indices, idescs); + CatalogIndexInsert(idescs, Num_pg_trigger_indices, tgrel, tuple); + CatalogCloseIndices(Num_pg_trigger_indices, idescs); + + /* + * Invalidate relation's relcache entry so that other + * backends (and this one too!) are sent SI message to make them + * rebuild relcache entries. + */ + CacheInvalidateRelcache(relid); + + found = TRUE; + break; + } + } + systable_endscan(tgscan); + + heap_close(tgrel, RowExclusiveLock); + + if (!found) + elog(ERROR, "renametrig: trigger %s not defined on relation %s", + oldname, RelationGetRelationName(targetrel)); + + /* + * Close rel, but keep exclusive lock! + */ + heap_close(targetrel, NoLock); +} + /* * Build trigger data to attach to the given relcache entry. * diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c index eb80acbd008..d9d219edd00 100644 --- a/src/backend/commands/user.c +++ b/src/backend/commands/user.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Header: /cvsroot/pgsql/src/backend/commands/user.c,v 1.96 2002/04/18 21:16:16 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/user.c,v 1.97 2002/04/26 19:29:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -646,7 +646,7 @@ CreateUser(CreateUserStmt *stmt) /* * ALTER USER */ -extern void +void AlterUser(AlterUserStmt *stmt) { Datum new_record[Natts_pg_shadow]; diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h index f3dfcd6b9b1..1ce35eb1648 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: tablecmds.h,v 1.2 2002/04/24 02:48:55 momjian Exp $ + * $Id: tablecmds.h,v 1.3 2002/04/26 19:29:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -61,8 +61,4 @@ extern void renameatt(Oid relid, extern void renamerel(Oid relid, const char *newrelname); -extern void renametrig(Oid relid, - const char *oldname, - const char *newname); - #endif /* TABLECMDS_H */ diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 4166b47bb74..4719bf0c914 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: trigger.h,v 1.34 2002/04/01 22:36:13 tgl Exp $ + * $Id: trigger.h,v 1.35 2002/04/26 19:29:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -105,6 +105,8 @@ extern void CreateTrigger(CreateTrigStmt *stmt); extern void DropTrigger(Oid relid, const char *trigname); extern void RelationRemoveTriggers(Relation rel); +extern void renametrig(Oid relid, const char *oldname, const char *newname); + extern void RelationBuildTriggers(Relation relation); extern void FreeTriggerDesc(TriggerDesc *trigdesc);