diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index e8b0d8bc013b3b527394e080a70069e220064d3c..7bdc238e77c635eed006053165daf3e166c9191f 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,902 +73,955 @@ 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. +/* ---------------------------------------------------------------- + * DefineRelation + * Creates a new relation. * - * 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 - * ---------------- + * If successful, returns the OID of the new relation. + * ---------------------------------------------------------------- */ -void -AlterTableAddColumn(Oid myrelid, - bool inherits, - ColumnDef *colDef) +Oid +DefineRelation(CreateStmt *stmt, char relkind) { - Relation rel, - pgclass, - attrdesc; - HeapTuple reltup; - HeapTuple newreltup; - HeapTuple attributeTuple; - Form_pg_attribute attribute; - FormData_pg_attribute attributeD; + char relname[NAMEDATALEN]; + Oid namespaceId; + List *schema = stmt->tableElts; + int numberOfAttributes; + Oid relationId; + Relation rel; + TupleDesc descriptor; + List *inheritOids; + List *old_constraints; + bool parentHasOids; + List *rawDefaults; + List *listptr; int i; - int minattnum, - maxatts; - HeapTuple typeTuple; - Form_pg_type tform; - int attndims; + AttrNumber attnum; /* - * Grab an exclusive lock on the target table, which we will NOT - * release until end of transaction. + * Truncate relname to appropriate length (probably a waste of time, + * as parser should have done this already). */ - rel = heap_open(myrelid, AccessExclusiveLock); + StrNCpy(relname, stmt->relation->relname, NAMEDATALEN); - if (rel->rd_rel->relkind != RELKIND_RELATION) - elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", - RelationGetRelationName(rel)); + /* + * Look up the namespace in which we are supposed to create the + * relation. + */ + namespaceId = RangeVarGetCreationNamespace(stmt->relation); /* - * 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. + * Merge domain attributes into the known columns before processing table + * inheritance. Otherwise we risk adding double constraints to a + * domain-type column that's inherited. */ - 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)); + schema = MergeDomainAttributes(schema); /* - * 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. + * Look up inheritance ancestors and generate relation schema, + * including inherited attributes. */ - if (inherits) - { - List *child, - *children; + schema = MergeAttributes(schema, stmt->inhRelations, + stmt->relation->istemp, + &inheritOids, &old_constraints, &parentHasOids); - /* this routine is actually in the planner */ - children = find_all_inheritors(myrelid); + numberOfAttributes = length(schema); + if (numberOfAttributes <= 0) + elog(ERROR, "DefineRelation: please inherit from a relation or define an attribute"); - /* - * 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) + /* + * Create a relation descriptor from the relation schema and create + * the relation. Note that in this stage only inherited (pre-cooked) + * defaults and constraints will be included into the new relation. + * (BuildDescForRelation takes care of the inherited defaults, but we + * have to copy inherited constraints here.) + */ + descriptor = BuildDescForRelation(schema); + + if (old_constraints != NIL) + { + ConstrCheck *check = (ConstrCheck *) palloc(length(old_constraints) * + sizeof(ConstrCheck)); + int ncheck = 0; + + foreach(listptr, old_constraints) { - Oid childrelid = lfirsti(child); + Constraint *cdef = (Constraint *) lfirst(listptr); - if (childrelid == myrelid) + if (cdef->contype != CONSTR_CHECK) continue; - AlterTableAddColumn(childrelid, false, colDef); + if (cdef->name != NULL) + { + for (i = 0; i < ncheck; i++) + { + if (strcmp(check[i].ccname, cdef->name) == 0) + elog(ERROR, "Duplicate CHECK constraint name: '%s'", + cdef->name); + } + check[ncheck].ccname = cdef->name; + } + else + { + check[ncheck].ccname = (char *) palloc(NAMEDATALEN); + snprintf(check[ncheck].ccname, NAMEDATALEN, "$%d", ncheck + 1); + } + Assert(cdef->raw_expr == NULL && cdef->cooked_expr != NULL); + check[ncheck].ccbin = pstrdup(cdef->cooked_expr); + ncheck++; + } + if (ncheck > 0) + { + if (descriptor->constr == NULL) + { + descriptor->constr = (TupleConstr *) palloc(sizeof(TupleConstr)); + descriptor->constr->defval = NULL; + descriptor->constr->num_defval = 0; + descriptor->constr->has_not_null = false; + } + descriptor->constr->num_check = ncheck; + descriptor->constr->check = check; } } + relationId = heap_create_with_catalog(relname, + namespaceId, + descriptor, + relkind, + stmt->hasoids || parentHasOids, + allowSystemTableMods); + + StoreCatalogInheritance(relationId, inheritOids); + /* - * 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 must bump the command counter to make the newly-created relation + * tuple visible for opening. + */ + CommandCounterIncrement(); + + /* + * Open the new relation and acquire exclusive lock on it. This isn't + * really necessary for locking out other backends (since they can't + * see the new rel anyway until we commit), but it keeps the lock + * manager from complaining about deadlock risks. + */ + rel = heap_open(relationId, AccessExclusiveLock); + + /* + * Now add any newly specified column default values and CHECK + * constraints to the new relation. These are passed to us in the + * form of raw parsetrees; we need to transform them to executable + * expression trees before they can be added. The most convenient way + * to do that is to apply the parser's transformExpr routine, but + * transformExpr doesn't work unless we have a pre-existing relation. + * So, the transformation has to be postponed to this final step of + * CREATE TABLE. * - * We do allow CHECK constraints, even though these theoretically could - * fail for NULL rows (eg, CHECK (newcol IS NOT NULL)). + * First, scan schema to find new column defaults. */ - 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."); + rawDefaults = NIL; + attnum = 0; - 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."); + foreach(listptr, schema) + { + ColumnDef *colDef = lfirst(listptr); + RawColumnDefault *rawEnt; - pgclass = heap_openr(RelationRelationName, RowExclusiveLock); + attnum++; - reltup = SearchSysCache(RELOID, - ObjectIdGetDatum(myrelid), - 0, 0, 0); - if (!HeapTupleIsValid(reltup)) - elog(ERROR, "ALTER TABLE: relation \"%s\" not found", - RelationGetRelationName(rel)); + if (colDef->raw_default == NULL) + continue; + Assert(colDef->cooked_default == NULL); - 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); + rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault)); + rawEnt->attnum = attnum; + rawEnt->raw_default = colDef->raw_default; + rawDefaults = lappend(rawDefaults, rawEnt); + } /* - * Update number of attributes in pg_class tuple + * Parse and add the defaults/constraints, if any. */ - 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! */ + if (rawDefaults || stmt->constraints) + AddRelationRawConstraints(rel, rawDefaults, stmt->constraints); /* - * Make our catalog updates visible for subsequent steps. + * Clean up. We keep lock on new relation (although it shouldn't be + * visible to anyone else anyway, until commit). */ - CommandCounterIncrement(); + heap_close(rel, NoLock); - /* - * 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); - } + return relationId; +} - /* - * Automatically create the secondary relation for TOAST if it - * formerly had no such but now has toastable attributes. - */ - AlterTableCreateToastTable(myrelid, true); +/* + * RemoveRelation + * Deletes a relation. + * + * Exceptions: + * BadArg if name is invalid. + * + * Note: + * If the relation has indices defined on it, then the index relations + * themselves will be destroyed, too. + */ +void +RemoveRelation(const RangeVar *relation) +{ + Oid relOid; + + relOid = RangeVarGetRelid(relation, false); + heap_drop_with_catalog(relOid, allowSystemTableMods); } /* - * ALTER TABLE ALTER COLUMN DROP NOT NULL + * TruncateRelation + * Removes all the rows from a relation + * + * Exceptions: + * BadArg if name is invalid + * + * Note: + * Rows are removed, indices are truncated and reconstructed. */ void -AlterTableAlterColumnDropNotNull(Oid myrelid, - bool inh, const char *colName) +TruncateRelation(const RangeVar *relation) { Relation rel; - HeapTuple tuple; - AttrNumber attnum; - Relation attr_rel; - List *indexoidlist; - List *indexoidscan; + Oid relid; - rel = heap_open(myrelid, AccessExclusiveLock); + /* Grab exclusive lock in preparation for truncate */ + rel = heap_openrv(relation, AccessExclusiveLock); + relid = RelationGetRelid(rel); - if (rel->rd_rel->relkind != RELKIND_RELATION) - elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", + if (rel->rd_rel->relkind == RELKIND_SEQUENCE) + elog(ERROR, "TRUNCATE cannot be used on sequences. '%s' is a sequence", RelationGetRelationName(rel)); - if (!allowSystemTableMods - && IsSystemRelation(rel)) - elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog", + if (rel->rd_rel->relkind == RELKIND_VIEW) + elog(ERROR, "TRUNCATE cannot be used on views. '%s' is a view", RelationGetRelationName(rel)); - if (!pg_class_ownercheck(myrelid, GetUserId())) - elog(ERROR, "ALTER TABLE: \"%s\": permission denied", + if (!allowSystemTableMods && IsSystemRelation(rel)) + elog(ERROR, "TRUNCATE cannot be used on system tables. '%s' is a system table", RelationGetRelationName(rel)); - /* - * Propagate to children if desired - */ - if (inh) - { - List *child, - *children; + if (!pg_class_ownercheck(relid, GetUserId())) + elog(ERROR, "you do not own relation \"%s\"", + RelationGetRelationName(rel)); - /* this routine is actually in the planner */ - children = find_all_inheritors(myrelid); + /* Keep the lock until transaction commit */ + heap_close(rel, NoLock); - /* - * 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); + heap_truncate(relid); +} - if (childrelid == myrelid) - continue; - AlterTableAlterColumnDropNotNull(childrelid, - false, colName); - } - } - /* -= now do the thing on this relation =- */ +/* + * MergeDomainAttributes + * Returns a new table schema with the constraints, types, and other + * attributes of domains resolved for fields using a domain as + * their type. + */ +static List * +MergeDomainAttributes(List *schema) +{ + List *entry; /* - * get the number of the attribute + * Loop through the table elements supplied. These should + * never include inherited domains else they'll be + * double (or more) processed. */ - 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); + foreach(entry, schema) + { + ColumnDef *coldef = lfirst(entry); + HeapTuple tuple; + Form_pg_type typeTup; - /* Prevent them from altering a system attribute */ - if (attnum < 0) - elog(ERROR, "ALTER TABLE: Cannot alter system attribute \"%s\"", - colName); + tuple = typenameType(coldef->typename); + typeTup = (Form_pg_type) GETSTRUCT(tuple); - /* - * Check that the attribute is not in a primary key - */ + if (typeTup->typtype == 'd') + { + /* Force the column to have the correct typmod. */ + coldef->typename->typmod = typeTup->typtypmod; + /* XXX more to do here? */ + } - /* Loop over all indices on the relation */ - indexoidlist = RelationGetIndexList(rel); + /* Enforce type NOT NULL || column definition NOT NULL -> NOT NULL */ + /* Currently only used for domains, but could be valid for all */ + coldef->is_not_null |= typeTup->typnotnull; - foreach(indexoidscan, indexoidlist) - { - Oid indexoid = lfirsti(indexoidscan); - HeapTuple indexTuple; - Form_pg_index indexStruct; - int i; + ReleaseSysCache(tuple); + } - 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); + return schema; +} - /* 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 +/*---------- + * MergeAttributes + * Returns new schema given initial schema and superclasses. + * + * Input arguments: + * 'schema' is the column/attribute definition for the table. (It's a list + * of ColumnDef's.) It is destructively changed. + * 'supers' is a list of names (as RangeVar nodes) of parent relations. + * 'istemp' is TRUE if we are creating a temp relation. + * + * Output arguments: + * 'supOids' receives an integer list of the OIDs of the parent relations. + * 'supconstr' receives a list of constraints belonging to the parents, + * updated as necessary to be valid for the child. + * 'supHasOids' is set TRUE if any parent has OIDs, else it is set FALSE. + * + * Return value: + * Completed schema list. + * + * Notes: + * The order in which the attributes are inherited is very important. + * Intuitively, the inherited attributes should come first. If a table + * inherits from multiple parents, the order of those attributes are + * according to the order of the parents specified in CREATE TABLE. + * + * Here's an example: + * + * create table person (name text, age int4, location point); + * create table emp (salary int4, manager text) inherits(person); + * create table student (gpa float8) inherits (person); + * create table stud_emp (percent int4) inherits (emp, student); + * + * The order of the attributes of stud_emp is: + * + * person {1:name, 2:age, 3:location} + * / \ + * {6:gpa} student emp {4:salary, 5:manager} + * \ / + * stud_emp {7:percent} + * + * If the same attribute name appears multiple times, then it appears + * in the result table in the proper location for its first appearance. + * + * Constraints (including NOT NULL constraints) for the child table + * are the union of all relevant constraints, from both the child schema + * and parent tables. + * + * The default value for a child column is defined as: + * (1) If the child schema specifies a default, that value is used. + * (2) If neither the child nor any parent specifies a default, then + * the column will not have a default. + * (3) If conflicting defaults are inherited from different parents + * (and not overridden by the child), an error is raised. + * (4) Otherwise the inherited default is used. + * Rule (3) is new in Postgres 7.1; in earlier releases you got a + * rather arbitrary choice of which parent default to use. + *---------- */ -void -AlterTableAlterColumnSetNotNull(Oid myrelid, - bool inh, const char *colName) +static List * +MergeAttributes(List *schema, List *supers, bool istemp, + List **supOids, List **supconstr, bool *supHasOids) { - 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)); + List *entry; + List *inhSchema = NIL; + List *parentOids = NIL; + List *constraints = NIL; + bool parentHasOids = false; + bool have_bogus_defaults = false; + char *bogus_marker = "Bogus!"; /* marks conflicting + * defaults */ + int child_attno; /* - * Propagate to children if desired + * Check for duplicate names in the explicit list of attributes. + * + * Although we might consider merging such entries in the same way that + * we handle name conflicts for inherited attributes, it seems to make + * more sense to assume such conflicts are errors. */ - if (inh) + foreach(entry, schema) { - List *child, - *children; - - /* this routine is actually in the planner */ - children = find_all_inheritors(myrelid); + ColumnDef *coldef = lfirst(entry); + List *rest; - /* - * 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) + foreach(rest, lnext(entry)) { - Oid childrelid = lfirsti(child); + ColumnDef *restdef = lfirst(rest); - if (childrelid == myrelid) - continue; - AlterTableAlterColumnSetNotNull(childrelid, - false, colName); + if (strcmp(coldef->colname, restdef->colname) == 0) + elog(ERROR, "CREATE TABLE: attribute \"%s\" duplicated", + coldef->colname); } } - /* -= now do the thing on this relation =- */ - /* - * get the number of the attribute + * Scan the parents left-to-right, and merge their attributes to form + * a list of inherited attributes (inhSchema). Also check to see if + * we need to inherit an OID column. */ - 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); + child_attno = 0; + foreach(entry, supers) + { + RangeVar *parent = (RangeVar *) lfirst(entry); + Relation relation; + TupleDesc tupleDesc; + TupleConstr *constr; + AttrNumber *newattno; + AttrNumber parent_attno; - attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum; - ReleaseSysCache(tuple); + relation = heap_openrv(parent, AccessShareLock); - /* Prevent them from altering a system attribute */ - if (attnum < 0) - elog(ERROR, "ALTER TABLE: Cannot alter system attribute \"%s\"", - colName); + if (relation->rd_rel->relkind != RELKIND_RELATION) + elog(ERROR, "CREATE TABLE: inherited relation \"%s\" is not a table", + parent->relname); + /* Permanent rels cannot inherit from temporary ones */ + if (!istemp && isTempNamespace(RelationGetNamespace(relation))) + elog(ERROR, "CREATE TABLE: cannot inherit from temp relation \"%s\"", + parent->relname); - /* - * Perform a scan to ensure that there are no NULL - * values already in the relation - */ - tupdesc = RelationGetDescr(rel); + /* + * We should have an UNDER permission flag for this, but for now, + * demand that creator of a child table own the parent. + */ + if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId())) + elog(ERROR, "you do not own table \"%s\"", + parent->relname); - scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL); + /* + * Reject duplications in the list of parents. + */ + if (intMember(RelationGetRelid(relation), parentOids)) + elog(ERROR, "CREATE TABLE: inherited relation \"%s\" duplicated", + parent->relname); - while (HeapTupleIsValid(tuple = heap_getnext(scan, 0))) - { - Datum d; - bool isnull; + parentOids = lappendi(parentOids, RelationGetRelid(relation)); + setRelhassubclassInRelation(RelationGetRelid(relation), true); - d = heap_getattr(tuple, attnum, tupdesc, &isnull); + parentHasOids |= relation->rd_rel->relhasoids; - if (isnull) - elog(ERROR, "ALTER TABLE: Attribute \"%s\" contains NULL values", - colName); - } + tupleDesc = RelationGetDescr(relation); + constr = tupleDesc->constr; - heap_endscan(scan); + /* + * newattno[] will contain the child-table attribute numbers for + * the attributes of this parent table. (They are not the same + * for parents after the first one.) + */ + newattno = (AttrNumber *) palloc(tupleDesc->natts * sizeof(AttrNumber)); - /* - * 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); + for (parent_attno = 1; parent_attno <= tupleDesc->natts; + parent_attno++) + { + Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1]; + char *attributeName = NameStr(attribute->attname); + int exist_attno; + ColumnDef *def; + TypeName *typename; - ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = TRUE; + /* + * Does it conflict with some previously inherited column? + */ + exist_attno = findAttrByName(attributeName, inhSchema); + if (exist_attno > 0) + { + /* + * Yes, try to merge the two column definitions. They must + * have the same type and typmod. + */ + elog(NOTICE, "CREATE TABLE: merging multiple inherited definitions of attribute \"%s\"", + attributeName); + def = (ColumnDef *) nth(exist_attno - 1, inhSchema); + if (typenameTypeId(def->typename) != attribute->atttypid || + def->typename->typmod != attribute->atttypmod) + elog(ERROR, "CREATE TABLE: inherited attribute \"%s\" type conflict (%s and %s)", + attributeName, + TypeNameToString(def->typename), + typeidTypeName(attribute->atttypid)); + /* Merge of NOT NULL constraints = OR 'em together */ + def->is_not_null |= attribute->attnotnull; + /* Default and other constraints are handled below */ + newattno[parent_attno - 1] = exist_attno; + } + else + { + /* + * No, create a new inherited column + */ + def = makeNode(ColumnDef); + def->colname = pstrdup(attributeName); + typename = makeNode(TypeName); + typename->typeid = attribute->atttypid; + typename->typmod = attribute->atttypmod; + def->typename = typename; + def->is_not_null = attribute->attnotnull; + def->raw_default = NULL; + def->cooked_default = NULL; + def->constraints = NIL; + inhSchema = lappend(inhSchema, def); + newattno[parent_attno - 1] = ++child_attno; + } - simple_heap_update(attr_rel, &tuple->t_self, tuple); + /* + * Copy default if any + */ + if (attribute->atthasdef) + { + char *this_default = NULL; + AttrDefault *attrdef; + int i; - /* keep the system catalog indices current */ - if (RelationGetForm(attr_rel)->relhasindex) - { - Relation idescs[Num_pg_attr_indices]; + /* Find default in constraint structure */ + Assert(constr != NULL); + attrdef = constr->defval; + for (i = 0; i < constr->num_defval; i++) + { + if (attrdef[i].adnum == parent_attno) + { + this_default = attrdef[i].adbin; + break; + } + } + Assert(this_default != NULL); - 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); - } + /* + * If default expr could contain any vars, we'd need to + * fix 'em, but it can't; so default is ready to apply to + * child. + * + * If we already had a default from some prior parent, check + * to see if they are the same. If so, no problem; if + * not, mark the column as having a bogus default. Below, + * we will complain if the bogus default isn't overridden + * by the child schema. + */ + Assert(def->raw_default == NULL); + if (def->cooked_default == NULL) + def->cooked_default = pstrdup(this_default); + else if (strcmp(def->cooked_default, this_default) != 0) + { + def->cooked_default = bogus_marker; + have_bogus_defaults = true; + } + } + } - heap_close(attr_rel, RowExclusiveLock); + /* + * Now copy the constraints of this parent, adjusting attnos using + * the completed newattno[] map + */ + if (constr && constr->num_check > 0) + { + ConstrCheck *check = constr->check; + int i; - heap_close(rel, NoLock); -} + for (i = 0; i < constr->num_check; i++) + { + Constraint *cdef = makeNode(Constraint); + Node *expr; + cdef->contype = CONSTR_CHECK; + if (check[i].ccname[0] == '$') + cdef->name = NULL; + else + cdef->name = pstrdup(check[i].ccname); + cdef->raw_expr = NULL; + /* adjust varattnos of ccbin here */ + expr = stringToNode(check[i].ccbin); + change_varattnos_of_a_node(expr, newattno); + cdef->cooked_expr = nodeToString(expr); + constraints = lappend(constraints, cdef); + } + } -/* - * ALTER TABLE ALTER COLUMN SET/DROP DEFAULT - */ -void -AlterTableAlterColumnDefault(Oid myrelid, - bool inh, const char *colName, - Node *newDefault) -{ - Relation rel; - HeapTuple tuple; - AttrNumber attnum; + pfree(newattno); - rel = heap_open(myrelid, AccessExclusiveLock); + /* + * Close the parent rel, but keep our AccessShareLock on it until + * xact commit. That will prevent someone else from deleting or + * ALTERing the parent before the child is committed. + */ + heap_close(relation, NoLock); + } /* - * 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 we had no inherited attributes, the result schema is just the + * explicitly declared columns. Otherwise, we need to merge the + * declared columns into the inherited schema list. */ - 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 (inhSchema != NIL) + { + foreach(entry, schema) + { + ColumnDef *newdef = lfirst(entry); + char *attributeName = newdef->colname; + int exist_attno; - if (!allowSystemTableMods - && IsSystemRelation(rel)) - elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog", - RelationGetRelationName(rel)); + /* + * Does it conflict with some previously inherited column? + */ + exist_attno = findAttrByName(attributeName, inhSchema); + if (exist_attno > 0) + { + ColumnDef *def; - if (!pg_class_ownercheck(myrelid, GetUserId())) - elog(ERROR, "ALTER TABLE: \"%s\": permission denied", - RelationGetRelationName(rel)); + /* + * Yes, try to merge the two column definitions. They must + * have the same type and typmod. + */ + elog(NOTICE, "CREATE TABLE: merging attribute \"%s\" with inherited definition", + attributeName); + def = (ColumnDef *) nth(exist_attno - 1, inhSchema); + if (typenameTypeId(def->typename) != typenameTypeId(newdef->typename) || + def->typename->typmod != newdef->typename->typmod) + elog(ERROR, "CREATE TABLE: attribute \"%s\" type conflict (%s and %s)", + attributeName, + TypeNameToString(def->typename), + TypeNameToString(newdef->typename)); + /* Merge of NOT NULL constraints = OR 'em together */ + def->is_not_null |= newdef->is_not_null; + /* If new def has a default, override previous default */ + if (newdef->raw_default != NULL) + { + def->raw_default = newdef->raw_default; + def->cooked_default = newdef->cooked_default; + } + } + else + { + /* + * No, attach new column to result schema + */ + inhSchema = lappend(inhSchema, newdef); + } + } + + schema = inhSchema; + } /* - * Propagate to children if desired + * If we found any conflicting parent default values, check to make + * sure they were overridden by the child. */ - if (inh) + if (have_bogus_defaults) { - 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) + foreach(entry, schema) { - Oid childrelid = lfirsti(child); + ColumnDef *def = lfirst(entry); - if (childrelid == myrelid) - continue; - AlterTableAlterColumnDefault(childrelid, - false, colName, newDefault); + if (def->cooked_default == bogus_marker) + elog(ERROR, "CREATE TABLE: attribute \"%s\" inherits conflicting default values" + "\n\tTo resolve the conflict, specify a default explicitly", + def->colname); } } - /* -= now do the thing on this relation =- */ + *supOids = parentOids; + *supconstr = constraints; + *supHasOids = parentHasOids; + return schema; +} - /* - * 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 +/* + * complementary static functions for MergeAttributes(). + * + * Varattnos of pg_relcheck.rcbin must be rewritten when subclasses inherit + * constraints from parent classes, since the inherited attributes could + * be given different column numbers in multiple-inheritance cases. + * + * Note that the passed node tree is modified in place! + */ +static bool +change_varattnos_walker(Node *node, const AttrNumber *newattno) +{ + if (node == NULL) + return false; + if (IsA(node, Var)) { - /* 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); + Var *var = (Var *) node; - /* keep the system catalog indices current */ - if (RelationGetForm(attr_rel)->relhasindex) + if (var->varlevelsup == 0 && var->varno == 1 && + var->varattno > 0) { - 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); + /* + * ??? the following may be a problem when the node is + * multiply referenced though stringToNode() doesn't create + * such a node currently. + */ + Assert(newattno[var->varattno - 1] > 0); + var->varattno = newattno[var->varattno - 1]; } - - heap_close(attr_rel, RowExclusiveLock); - - /* get rid of actual default definition in pg_attrdef */ - drop_default(myrelid, attnum); + return false; } - - heap_close(rel, NoLock); + return expression_tree_walker(node, change_varattnos_walker, + (void *) newattno); } - -static void -drop_default(Oid relid, int16 attnum) +static bool +change_varattnos_of_a_node(Node *node, const AttrNumber *newattno) { - 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); + return change_varattnos_walker(node, newattno); } - /* - * ALTER TABLE ALTER COLUMN SET STATISTICS / STORAGE + * StoreCatalogInheritance + * Updates the system catalogs with proper inheritance information. + * + * supers is an integer list of the OIDs of the new relation's direct + * ancestors. NB: it is destructively changed to include indirect ancestors. */ -void -AlterTableAlterColumnFlags(Oid myrelid, - bool inh, const char *colName, - Node *flagValue, const char *flagType) +static void +StoreCatalogInheritance(Oid relationId, List *supers) { - Relation rel; - int newtarget = 1; - char newstorage = 'p'; - Relation attrelation; + Relation relation; + TupleDesc desc; + int16 seqNumber; + List *entry; 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 + * sanity checks */ - if (*flagType != 'S' && !allowSystemTableMods && IsSystemRelation(rel)) - elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog", - RelationGetRelationName(rel)); + AssertArg(OidIsValid(relationId)); - if (!pg_class_ownercheck(myrelid, GetUserId())) - elog(ERROR, "ALTER TABLE: \"%s\": permission denied", - RelationGetRelationName(rel)); + if (supers == NIL) + return; /* - * Check the supplied parameters before anything else + * Catalog INHERITS information using direct ancestors only. */ - if (*flagType == 'S') - { - /* STATISTICS */ - Assert(IsA(flagValue, Integer)); - newtarget = intVal(flagValue); + relation = heap_openr(InheritsRelationName, RowExclusiveLock); + desc = RelationGetDescr(relation); - /* - * 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') + seqNumber = 1; + foreach(entry, supers) { - /* STORAGE */ - char *storagemode; + Oid entryOid = lfirsti(entry); + Datum datum[Natts_pg_inherits]; + char nullarr[Natts_pg_inherits]; - Assert(IsA(flagValue, String)); - storagemode = strVal(flagValue); + datum[0] = ObjectIdGetDatum(relationId); /* inhrel */ + datum[1] = ObjectIdGetDatum(entryOid); /* inhparent */ + datum[2] = Int16GetDatum(seqNumber); /* inhseqno */ - 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); - } + nullarr[0] = ' '; + nullarr[1] = ' '; + nullarr[2] = ' '; - /* - * Propagate to children if desired - */ - if (inh) - { - List *child, - *children; + tuple = heap_formtuple(desc, datum, nullarr); - /* this routine is actually in the planner */ - children = find_all_inheritors(myrelid); + heap_insert(relation, tuple); - /* - * 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) + if (RelationGetForm(relation)->relhasindex) { - Oid childrelid = lfirsti(child); + Relation idescs[Num_pg_inherits_indices]; - if (childrelid == myrelid) - continue; - AlterTableAlterColumnFlags(childrelid, - false, colName, flagValue, flagType); + CatalogOpenIndices(Num_pg_inherits_indices, Name_pg_inherits_indices, idescs); + CatalogIndexInsert(idescs, Num_pg_inherits_indices, relation, tuple); + CatalogCloseIndices(Num_pg_inherits_indices, idescs); } - } - /* -= now do the thing on this relation =- */ + heap_freetuple(tuple); - attrelation = heap_openr(AttributeRelationName, RowExclusiveLock); + seqNumber += 1; + } - 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); + heap_close(relation, RowExclusiveLock); - 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') + /* ---------------- + * Expand supers list to include indirect ancestors as well. + * + * Algorithm: + * 0. begin with list of direct superclasses. + * 1. append after each relationId, its superclasses, recursively. + * 2. remove all but last of duplicates. + * ---------------- + */ + + /* + * 1. append after each relationId, its superclasses, recursively. + */ + foreach(entry, supers) { - /* - * 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)); - } + HeapTuple tuple; + Oid id; + int16 number; + List *next; + List *current; - simple_heap_update(attrelation, &tuple->t_self, tuple); + id = (Oid) lfirsti(entry); + current = entry; + next = lnext(entry); - /* keep system catalog indices current */ - { - Relation irelations[Num_pg_attr_indices]; + for (number = 1;; number += 1) + { + tuple = SearchSysCache(INHRELID, + ObjectIdGetDatum(id), + Int16GetDatum(number), + 0, 0); + if (!HeapTupleIsValid(tuple)) + break; - CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, irelations); - CatalogIndexInsert(irelations, Num_pg_attr_indices, attrelation, tuple); - CatalogCloseIndices(Num_pg_attr_indices, irelations); + lnext(current) = lconsi(((Form_pg_inherits) + GETSTRUCT(tuple))->inhparent, + NIL); + + ReleaseSysCache(tuple); + + current = lnext(current); + } + lnext(current) = next; } - heap_freetuple(tuple); - heap_close(attrelation, NoLock); - heap_close(rel, NoLock); /* close rel, but keep lock! */ -} + /* + * 2. remove all but last of duplicates. + */ + foreach(entry, supers) + { + Oid thisone; + bool found; + List *rest; +again: + thisone = lfirsti(entry); + found = false; + foreach(rest, lnext(entry)) + { + if (thisone == lfirsti(rest)) + { + found = true; + break; + } + } + if (found) + { + /* + * found a later duplicate, so remove this entry. + */ + lfirsti(entry) = lfirsti(lnext(entry)); + lnext(entry) = lnext(lnext(entry)); + goto again; + } + } +} /* - * ALTER TABLE DROP COLUMN + * Look for an existing schema entry with the given name. + * + * Returns the index (starting with 1) if attribute already exists in schema, + * 0 if it doesn't. */ -void -AlterTableDropColumn(Oid myrelid, - bool inh, const char *colName, - int behavior) +static int +findAttrByName(const char *attributeName, List *schema) { - elog(ERROR, "ALTER TABLE / DROP COLUMN is not implemented"); + List *s; + int i = 0; + + foreach(s, schema) + { + ColumnDef *def = lfirst(s); + + ++i; + if (strcmp(attributeName, def->colname) == 0) + return i; + } + return 0; } +/* + * Update a relation's pg_class.relhassubclass entry to the given value + */ +static void +setRelhassubclassInRelation(Oid relationId, bool relhassubclass) +{ + Relation relationRelation; + HeapTuple tuple; + Relation idescs[Num_pg_class_indices]; + + /* + * Fetch a modifiable copy of the tuple, modify it, update pg_class. + */ + relationRelation = heap_openr(RelationRelationName, RowExclusiveLock); + tuple = SearchSysCacheCopy(RELOID, + ObjectIdGetDatum(relationId), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "setRelhassubclassInRelation: cache lookup failed for relation %u", relationId); + + ((Form_pg_class) GETSTRUCT(tuple))->relhassubclass = relhassubclass; + simple_heap_update(relationRelation, &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, relationRelation, tuple); + CatalogCloseIndices(Num_pg_class_indices, idescs); + + heap_freetuple(tuple); + heap_close(relationRelation, RowExclusiveLock); +} /* - * ALTER TABLE ADD CONSTRAINT + * renameatt - changes the name of a attribute in a relation + * + * Attname attribute is changed in attribute catalog. + * No record of the previous attname is kept (correct?). + * + * get proper relrelation from relation catalog (if not arg) + * scan attribute catalog + * for name conflict (within rel) + * for original attribute (if not arg) + * modify attname in attribute tuple + * insert modified attribute in attribute catalog + * delete original attribute from attribute catalog */ void -AlterTableAddConstraint(Oid myrelid, - bool inh, List *newConstraints) +renameatt(Oid relid, + const char *oldattname, + const char *newattname, + bool recurse) { - Relation rel; - List *listptr; + Relation targetrelation; + Relation attrelation; + HeapTuple atttup; + List *indexoidlist; + List *indexoidscan; /* * 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)); + targetrelation = heap_open(relid, AccessExclusiveLock); - if (!pg_class_ownercheck(myrelid, GetUserId())) - elog(ERROR, "ALTER TABLE: \"%s\": permission denied", - 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(targetrelation)) + elog(ERROR, "renameatt: class \"%s\" is a system catalog", + RelationGetRelationName(targetrelation)); + if (!pg_class_ownercheck(relid, GetUserId())) + elog(ERROR, "renameatt: you do not own class \"%s\"", + RelationGetRelationName(targetrelation)); - if (inh) + /* + * if the 'recurse' flag is set then we are supposed to rename this + * attribute in all classes that inherit from 'relname' (as well as in + * 'relname'). + * + * any permissions or problems with duplicate attributes will cause the + * whole transaction to abort, which is what we want -- all or + * nothing. + */ + if (recurse) { List *child, *children; /* this routine is actually in the planner */ - children = find_all_inheritors(myrelid); + children = find_all_inheritors(relid); /* * find_all_inheritors does the recursive search of the @@ -980,1641 +1032,1354 @@ AlterTableAddConstraint(Oid myrelid, { Oid childrelid = lfirsti(child); - if (childrelid == myrelid) + if (childrelid == relid) continue; - AlterTableAddConstraint(childrelid, false, newConstraints); + /* note we need not recurse again! */ + renameatt(childrelid, oldattname, newattname, false); } } - foreach(listptr, newConstraints) - { - Node *newConstraint = lfirst(listptr); + attrelation = heap_openr(AttributeRelationName, RowExclusiveLock); - switch (nodeTag(newConstraint)) - { - case T_Constraint: - { - Constraint *constr = (Constraint *) newConstraint; + atttup = SearchSysCacheCopy(ATTNAME, + ObjectIdGetDatum(relid), + PointerGetDatum(oldattname), + 0, 0); + if (!HeapTupleIsValid(atttup)) + elog(ERROR, "renameatt: attribute \"%s\" does not exist", oldattname); - /* - * 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 (((Form_pg_attribute) GETSTRUCT(atttup))->attnum < 0) + elog(ERROR, "renameatt: system attribute \"%s\" not renamed", oldattname); - if (constr->name) - name = constr->name; - else - name = "<unnamed>"; - - /* - * 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."); + /* should not already exist */ + if (SearchSysCacheExists(ATTNAME, + ObjectIdGetDatum(relid), + PointerGetDatum(newattname), + 0, 0)) + elog(ERROR, "renameatt: attribute \"%s\" exists", newattname); - /* - * 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 = "<unknown>"; - trig.tgenabled = TRUE; - trig.tgisconstraint = TRUE; - trig.tgconstrrelid = RelationGetRelid(pkrel); - trig.tgdeferrable = FALSE; - trig.tginitdeferred = FALSE; + namestrcpy(&(((Form_pg_attribute) GETSTRUCT(atttup))->attname), + newattname); - trig.tgargs = (char **) palloc( - sizeof(char *) * (4 + length(fkconstraint->fk_attrs) - + length(fkconstraint->pk_attrs))); + simple_heap_update(attrelation, &atttup->t_self, atttup); - 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); + /* keep system catalog indices current */ + { + Relation irelations[Num_pg_attr_indices]; - trig.tgargs[count] = fk_at->name; - count += 2; - } - count = 5; - foreach(list, fkconstraint->pk_attrs) - { - Ident *pk_at = lfirst(list); + CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, irelations); + CatalogIndexInsert(irelations, Num_pg_attr_indices, attrelation, atttup); + CatalogCloseIndices(Num_pg_attr_indices, irelations); + } - trig.tgargs[count] = pk_at->name; - count += 2; - } - trig.tgnargs = count - 1; + heap_freetuple(atttup); - scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL); + /* + * Update column names of indexes that refer to the column being + * renamed. + */ + indexoidlist = RelationGetIndexList(targetrelation); - while (HeapTupleIsValid(tuple = heap_getnext(scan, 0))) - { - /* Make a call to the check function */ + foreach(indexoidscan, indexoidlist) + { + Oid indexoid = lfirsti(indexoidscan); + HeapTuple indextup; - /* - * No parameters are passed, but we do set a - * context - */ - FunctionCallInfoData fcinfo; - TriggerData trigdata; + /* + * First check to see if index is a functional index. If so, its + * column name is a function name and shouldn't be renamed here. + */ + indextup = SearchSysCache(INDEXRELID, + ObjectIdGetDatum(indexoid), + 0, 0, 0); + if (!HeapTupleIsValid(indextup)) + elog(ERROR, "renameatt: can't find index id %u", indexoid); + if (OidIsValid(((Form_pg_index) GETSTRUCT(indextup))->indproc)) + { + ReleaseSysCache(indextup); + continue; + } + ReleaseSysCache(indextup); - MemSet(&fcinfo, 0, sizeof(fcinfo)); + /* + * Okay, look to see if any column name of the index matches the + * old attribute name. + */ + atttup = SearchSysCacheCopy(ATTNAME, + ObjectIdGetDatum(indexoid), + PointerGetDatum(oldattname), + 0, 0); + if (!HeapTupleIsValid(atttup)) + continue; /* Nope, so ignore it */ - /* - * We assume RI_FKey_check_ins won't look at - * flinfo... - */ + /* + * Update the (copied) attribute tuple. + */ + namestrcpy(&(((Form_pg_attribute) GETSTRUCT(atttup))->attname), + newattname); - 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; + simple_heap_update(attrelation, &atttup->t_self, atttup); - fcinfo.context = (Node *) &trigdata; + /* keep system catalog indices current */ + { + Relation irelations[Num_pg_attr_indices]; - RI_FKey_check_ins(&fcinfo); - } - heap_endscan(scan); + CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, irelations); + CatalogIndexInsert(irelations, Num_pg_attr_indices, attrelation, atttup); + CatalogCloseIndices(Num_pg_attr_indices, irelations); + } + heap_freetuple(atttup); + } - pfree(trig.tgargs); + freeList(indexoidlist); - heap_close(pkrel, NoLock); + heap_close(attrelation, RowExclusiveLock); - break; - } - default: - elog(ERROR, "ALTER TABLE / ADD CONSTRAINT unable to determine type of constraint passed"); - } + /* + * Update att name in any RI triggers associated with the relation. + */ + if (targetrelation->rd_rel->reltriggers > 0) + { + /* update tgargs column reference where att is primary key */ + update_ri_trigger_args(RelationGetRelid(targetrelation), + oldattname, newattname, + false, false); + /* update tgargs column reference where att is foreign key */ + update_ri_trigger_args(RelationGetRelid(targetrelation), + oldattname, newattname, + true, false); } - /* Close rel, but keep lock till commit */ - heap_close(rel, NoLock); + heap_close(targetrelation, NoLock); /* close rel but keep lock! */ } - - /* - * 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 + * renamerel - change the name of a relation + * + * XXX - When renaming sequences, we don't bother to modify the + * sequence name that is stored within the sequence itself + * (this would cause problems with MVCC). In the future, + * the sequence name should probably be removed from the + * sequence, AFAIK there's no need for it to be there. */ void -AlterTableDropConstraint(Oid myrelid, - bool inh, const char *constrName, - int behavior) +renamerel(Oid relid, const char *newrelname) { - Relation rel; - int deleted; + Relation targetrelation; + Relation relrelation; /* for RELATION relation */ + HeapTuple reltup; + Oid namespaceId; + char *oldrelname; + char relkind; + bool relhastriggers; + Relation irelations[Num_pg_class_indices]; /* - * We don't support CASCADE yet - in fact, RESTRICT doesn't work to - * the spec either! + * Grab an exclusive lock on the target table or index, which we will + * NOT release until end of transaction. */ - if (behavior == CASCADE) - elog(ERROR, "ALTER TABLE / DROP CONSTRAINT does not support the CASCADE keyword"); + targetrelation = relation_open(relid, AccessExclusiveLock); + + oldrelname = pstrdup(RelationGetRelationName(targetrelation)); + namespaceId = RelationGetNamespace(targetrelation); + + /* Validity checks */ + if (!allowSystemTableMods && + IsSystemRelation(targetrelation)) + elog(ERROR, "renamerel: system relation \"%s\" may not be renamed", + oldrelname); + + relkind = targetrelation->rd_rel->relkind; + relhastriggers = (targetrelation->rd_rel->reltriggers > 0); /* - * Acquire an exclusive lock on the target relation for the duration - * of the operation. + * Find relation's pg_class tuple, and make sure newrelname isn't in + * use. */ - 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)); + relrelation = heap_openr(RelationRelationName, RowExclusiveLock); - if (!allowSystemTableMods - && IsSystemRelation(rel)) - elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog", - RelationGetRelationName(rel)); + reltup = SearchSysCacheCopy(RELOID, + PointerGetDatum(relid), + 0, 0, 0); + if (!HeapTupleIsValid(reltup)) + elog(ERROR, "renamerel: relation \"%s\" does not exist", + oldrelname); - if (!pg_class_ownercheck(myrelid, GetUserId())) - elog(ERROR, "ALTER TABLE: \"%s\": permission denied", - RelationGetRelationName(rel)); + if (get_relname_relid(newrelname, namespaceId) != InvalidOid) + elog(ERROR, "renamerel: relation \"%s\" exists", newrelname); /* - * 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. + * Update pg_class tuple with new relname. (Scribbling on reltup is + * OK because it's a copy...) */ + namestrcpy(&(((Form_pg_class) GETSTRUCT(reltup))->relname), newrelname); - deleted = 0; + simple_heap_update(relrelation, &reltup->t_self, reltup); + + /* keep the system catalog indices current */ + CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, irelations); + CatalogIndexInsert(irelations, Num_pg_class_indices, relrelation, reltup); + CatalogCloseIndices(Num_pg_class_indices, irelations); + + heap_close(relrelation, NoLock); + heap_freetuple(reltup); /* - * First, we remove all CHECK constraints with the given name + * Also rename the associated type, if any. */ + if (relkind != RELKIND_INDEX) + TypeRename(oldrelname, namespaceId, newrelname); - deleted += RemoveCheckConstraint(rel, constrName, inh); + /* + * Update rel name in any RI triggers associated with the relation. + */ + if (relhastriggers) + { + /* update tgargs where relname is primary key */ + update_ri_trigger_args(relid, + oldrelname, + newrelname, + false, true); + /* update tgargs where relname is foreign key */ + update_ri_trigger_args(relid, + oldrelname, + newrelname, + true, true); + } /* - * Now we remove NULL, UNIQUE, PRIMARY KEY and FOREIGN KEY - * constraints. - * - * Unimplemented. + * Close rel, but keep exclusive lock! */ + relation_close(targetrelation, NoLock); +} - /* 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"); +/* + * Given a trigger function OID, determine whether it is an RI trigger, + * and if so whether it is attached to PK or FK relation. + * + * XXX this probably doesn't belong here; should be exported by + * ri_triggers.c + */ +static int +ri_trigger_type(Oid tgfoid) +{ + switch (tgfoid) + { + case F_RI_FKEY_CASCADE_DEL: + case F_RI_FKEY_CASCADE_UPD: + case F_RI_FKEY_RESTRICT_DEL: + case F_RI_FKEY_RESTRICT_UPD: + case F_RI_FKEY_SETNULL_DEL: + case F_RI_FKEY_SETNULL_UPD: + case F_RI_FKEY_SETDEFAULT_DEL: + case F_RI_FKEY_SETDEFAULT_UPD: + case F_RI_FKEY_NOACTION_DEL: + case F_RI_FKEY_NOACTION_UPD: + return RI_TRIGGER_PK; + + case F_RI_FKEY_CHECK_INS: + case F_RI_FKEY_CHECK_UPD: + return RI_TRIGGER_FK; + } + + return RI_TRIGGER_NONE; } -/* - * 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; +/* + * Scan pg_trigger for RI triggers that are on the specified relation + * (if fk_scan is false) or have it as the tgconstrrel (if fk_scan + * is true). Update RI trigger args fields matching oldname to contain + * newname instead. If update_relname is true, examine the relname + * fields; otherwise examine the attname fields. + */ +static void +update_ri_trigger_args(Oid relid, + const char *oldname, + const char *newname, + bool fk_scan, + bool update_relname) +{ + Relation tgrel; + Relation irel; + ScanKeyData skey[1]; + IndexScanDesc idxtgscan; + RetrieveIndexResult idxres; + Datum values[Natts_pg_trigger]; + char nulls[Natts_pg_trigger]; + char replaces[Natts_pg_trigger]; + + tgrel = heap_openr(TriggerRelationName, RowExclusiveLock); + if (fk_scan) + irel = index_openr(TriggerConstrRelidIndex); + else + irel = index_openr(TriggerRelidNameIndex); + + ScanKeyEntryInitialize(&skey[0], 0x0, + 1, /* column 1 of index in either case */ + F_OIDEQ, + ObjectIdGetDatum(relid)); + idxtgscan = index_beginscan(irel, false, 1, skey); + + while ((idxres = index_getnext(idxtgscan, ForwardScanDirection)) != NULL) + { + HeapTupleData tupledata; + Buffer buffer; + HeapTuple tuple; + Form_pg_trigger pg_trigger; + bytea *val; + bytea *newtgargs; + bool isnull; + int tg_type; + bool examine_pk; + bool changed; + int tgnargs; + int i; + int newlen; + const char *arga[RI_MAX_ARGUMENTS]; + const char *argp; + + tupledata.t_self = idxres->heap_iptr; + heap_fetch(tgrel, SnapshotNow, &tupledata, &buffer, idxtgscan); + pfree(idxres); + if (!tupledata.t_data) + continue; + tuple = &tupledata; + pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple); + tg_type = ri_trigger_type(pg_trigger->tgfoid); + if (tg_type == RI_TRIGGER_NONE) + { + /* Not an RI trigger, forget it */ + ReleaseBuffer(buffer); + continue; + } + + /* + * It is an RI trigger, so parse the tgargs bytea. + * + * NB: we assume the field will never be compressed or moved out of + * line; so does trigger.c ... + */ + tgnargs = pg_trigger->tgnargs; + val = (bytea *) fastgetattr(tuple, + Anum_pg_trigger_tgargs, + tgrel->rd_att, &isnull); + if (isnull || tgnargs < RI_FIRST_ATTNAME_ARGNO || + tgnargs > RI_MAX_ARGUMENTS) + { + /* This probably shouldn't happen, but ignore busted triggers */ + ReleaseBuffer(buffer); + continue; + } + argp = (const char *) VARDATA(val); + for (i = 0; i < tgnargs; i++) + { + arga[i] = argp; + argp += strlen(argp) + 1; + } - /* Get exclusive lock till end of transaction on the target table */ - target_rel = heap_open(relationOid, AccessExclusiveLock); + /* + * Figure out which item(s) to look at. If the trigger is + * primary-key type and attached to my rel, I should look at the + * PK fields; if it is foreign-key type and attached to my rel, I + * should look at the FK fields. But the opposite rule holds when + * examining triggers found by tgconstrrel search. + */ + examine_pk = (tg_type == RI_TRIGGER_PK) == (!fk_scan); - /* Get its pg_class tuple, too */ - class_rel = heap_openr(RelationRelationName, RowExclusiveLock); + changed = false; + if (update_relname) + { + /* Change the relname if needed */ + i = examine_pk ? RI_PK_RELNAME_ARGNO : RI_FK_RELNAME_ARGNO; + if (strcmp(arga[i], oldname) == 0) + { + arga[i] = newname; + changed = true; + } + } + else + { + /* Change attname(s) if needed */ + i = examine_pk ? RI_FIRST_ATTNAME_ARGNO + RI_KEYPAIR_PK_IDX : + RI_FIRST_ATTNAME_ARGNO + RI_KEYPAIR_FK_IDX; + for (; i < tgnargs; i += 2) + { + if (strcmp(arga[i], oldname) == 0) + { + arga[i] = newname; + changed = true; + } + } + } - 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); + if (!changed) + { + /* Don't need to update this tuple */ + ReleaseBuffer(buffer); + continue; + } - /* Can we change the ownership of this tuple? */ - CheckTupleType(tuple_class); + /* + * Construct modified tgargs bytea. + */ + newlen = VARHDRSZ; + for (i = 0; i < tgnargs; i++) + newlen += strlen(arga[i]) + 1; + newtgargs = (bytea *) palloc(newlen); + VARATT_SIZEP(newtgargs) = newlen; + newlen = VARHDRSZ; + for (i = 0; i < tgnargs; i++) + { + strcpy(((char *) newtgargs) + newlen, arga[i]); + newlen += strlen(arga[i]) + 1; + } - /* - * 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); + /* + * Build modified tuple. + */ + for (i = 0; i < Natts_pg_trigger; i++) + { + values[i] = (Datum) 0; + replaces[i] = ' '; + nulls[i] = ' '; + } + values[Anum_pg_trigger_tgargs - 1] = PointerGetDatum(newtgargs); + replaces[Anum_pg_trigger_tgargs - 1] = 'r'; - /* 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); + tuple = heap_modifytuple(tuple, tgrel, values, nulls, replaces); - /* - * 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; + /* + * Now we can release hold on original tuple. + */ + ReleaseBuffer(buffer); - /* Find all the indexes belonging to this relation */ - index_oid_list = RelationGetIndexList(target_rel); + /* + * Update pg_trigger and its indexes + */ + simple_heap_update(tgrel, &tuple->t_self, tuple); - /* For each index, recursively change its ownership */ - foreach(i, index_oid_list) { - AlterTableOwner(lfirsti(i), newOwnerSysId); + Relation irelations[Num_pg_attr_indices]; + + CatalogOpenIndices(Num_pg_trigger_indices, Name_pg_trigger_indices, irelations); + CatalogIndexInsert(irelations, Num_pg_trigger_indices, tgrel, tuple); + CatalogCloseIndices(Num_pg_trigger_indices, irelations); } - freeList(index_oid_list); + /* free up our scratch memory */ + pfree(newtgargs); + heap_freetuple(tuple); } - 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); - } - } + index_endscan(idxtgscan); + index_close(irel); - heap_freetuple(tuple); - heap_close(class_rel, RowExclusiveLock); - heap_close(target_rel, NoLock); -} + heap_close(tgrel, RowExclusiveLock); -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)); - } + /* + * Increment cmd counter to make updates visible; this is needed in + * case the same tuple has to be updated again by next pass (can + * happen in case of a self-referential FK relationship). + */ + CommandCounterIncrement(); } -/* - * ALTER TABLE CREATE TOAST TABLE + +/* ---------------- + * 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 -AlterTableCreateToastTable(Oid relOid, bool silent) +AlterTableAddColumn(Oid myrelid, + bool inherits, + ColumnDef *colDef) { - Relation rel; + Relation rel, + pgclass, + attrdesc; 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]; + 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(relOid, AccessExclusiveLock); + 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 (!pg_class_ownercheck(relOid, GetUserId())) + /* + * 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)); /* - * lock the pg_class tuple for update (is that really needed?) + * 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. */ - class_rel = heap_openr(RelationRelationName, RowExclusiveLock); + 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(relOid), + ObjectIdGetDatum(myrelid), 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); + 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)); - /* - * 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; - } + 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; - elog(ERROR, "ALTER TABLE: relation \"%s\" already has a toast table", - RelationGetRelationName(rel)); - } + attrdesc = heap_openr(AttributeRelationName, RowExclusiveLock); - /* - * 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; - } + if (colDef->typename->arrayBounds) + attndims = length(colDef->typename->arrayBounds); + else + attndims = 0; - elog(ERROR, "ALTER TABLE: relation \"%s\" does not need a toast table", - RelationGetRelationName(rel)); - } + typeTuple = typenameType(colDef->typename); + tform = (Form_pg_type) GETSTRUCT(typeTuple); - /* - * Create the toast table and its index - */ - sprintf(toast_relname, "pg_toast_%u", relOid); - sprintf(toast_idxname, "pg_toast_%u_index", relOid); + attributeTuple = heap_addheader(Natts_pg_attribute, + ATTRIBUTE_TUPLE_SIZE, + (void *) &attributeD); - /* 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); + attribute = (Form_pg_attribute) GETSTRUCT(attributeTuple); - /* - * 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'; + 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); - /* - * 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); + ReleaseSysCache(typeTuple); - /* make the toast relation visible, else index creation will fail */ - CommandCounterIncrement(); + 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); /* - * 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. + * Update number of attributes in pg_class tuple */ + newreltup = heap_copytuple(reltup); - 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; + ((Form_pg_class) GETSTRUCT(newreltup))->relnatts = maxatts; + simple_heap_update(pgclass, &newreltup->t_self, newreltup); - classObjectId[0] = OID_BTREE_OPS_OID; - classObjectId[1] = INT4_BTREE_OPS_OID; + /* keep catalog indices current */ + if (RelationGetForm(pgclass)->relhasindex) + { + Relation ridescs[Num_pg_class_indices]; - toast_idxid = index_create(toast_relid, toast_idxname, indexInfo, - BTREE_AM_OID, classObjectId, - true, true); + CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, ridescs); + CatalogIndexInsert(ridescs, Num_pg_class_indices, pgclass, newreltup); + CatalogCloseIndices(Num_pg_class_indices, ridescs); + } - /* - * 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); + heap_freetuple(newreltup); + ReleaseSysCache(reltup); - /* - * 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); + heap_close(pgclass, NoLock); + + heap_close(rel, NoLock); /* close rel but keep lock! */ /* - * Keep catalog indices current + * Make our catalog updates visible for subsequent steps. */ - 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); + CommandCounterIncrement(); /* - * Close relations and make changes visible + * 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. */ - heap_close(class_rel, NoLock); - heap_close(rel, NoLock); + if (colDef->constraints != NIL) + { + rel = heap_open(myrelid, AccessExclusiveLock); + AddRelationRawConstraints(rel, NIL, colDef->constraints); + heap_close(rel, NoLock); + } - CommandCounterIncrement(); + /* + * Automatically create the secondary relation for TOAST if it + * formerly had no such but now has toastable attributes. + */ + AlterTableCreateToastTable(myrelid, true); } /* - * 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)".) + * ALTER TABLE ALTER COLUMN DROP NOT NULL */ -static bool -needs_toast_table(Relation rel) +void +AlterTableAlterColumnDropNotNull(Oid myrelid, + bool inh, const char *colName) { - int32 data_length = 0; - bool maxlength_unknown = false; - bool has_toastable_attrs = false; - TupleDesc tupdesc; - Form_pg_attribute *att; - int32 tuple_length; - int i; + Relation rel; + HeapTuple tuple; + AttrNumber attnum; + Relation attr_rel; + List *indexoidlist; + List *indexoidscan; - tupdesc = rel->rd_att; - att = tupdesc->attrs; + rel = heap_open(myrelid, AccessExclusiveLock); - for (i = 0; i < tupdesc->natts; i++) + 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) { - 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 + 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) { - int32 maxlen = type_maximum_size(att[i]->atttypid, - att[i]->atttypmod); + Oid childrelid = lfirsti(child); - if (maxlen < 0) - maxlength_unknown = true; - else - data_length += maxlen; - if (att[i]->attstorage != 'p') - has_toastable_attrs = true; + if (childrelid == myrelid) + continue; + AlterTableAlterColumnDropNotNull(childrelid, + false, colName); } } - 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. - * - * If successful, returns the OID of the new relation. - * ---------------------------------------------------------------- - */ -Oid -DefineRelation(CreateStmt *stmt, char relkind) -{ - char relname[NAMEDATALEN]; - Oid namespaceId; - List *schema = stmt->tableElts; - int numberOfAttributes; - Oid relationId; - Relation rel; - TupleDesc descriptor; - List *inheritOids; - List *old_constraints; - bool parentHasOids; - List *rawDefaults; - List *listptr; - int i; - AttrNumber attnum; + /* -= now do the thing on this relation =- */ /* - * Truncate relname to appropriate length (probably a waste of time, - * as parser should have done this already). + * get the number of the attribute */ - StrNCpy(relname, stmt->relation->relname, NAMEDATALEN); + 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); - /* - * Look up the namespace in which we are supposed to create the - * relation. - */ - namespaceId = RangeVarGetCreationNamespace(stmt->relation); + attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum; + ReleaseSysCache(tuple); - /* - * Merge domain attributes into the known columns before processing table - * inheritance. Otherwise we risk adding double constraints to a - * domain-type column that's inherited. - */ - schema = MergeDomainAttributes(schema); + /* Prevent them from altering a system attribute */ + if (attnum < 0) + elog(ERROR, "ALTER TABLE: Cannot alter system attribute \"%s\"", + colName); /* - * Look up inheritance ancestors and generate relation schema, - * including inherited attributes. + * Check that the attribute is not in a primary key */ - schema = MergeAttributes(schema, stmt->inhRelations, - stmt->relation->istemp, - &inheritOids, &old_constraints, &parentHasOids); - - numberOfAttributes = length(schema); - if (numberOfAttributes <= 0) - elog(ERROR, "DefineRelation: please inherit from a relation or define an attribute"); - /* - * Create a relation descriptor from the relation schema and create - * the relation. Note that in this stage only inherited (pre-cooked) - * defaults and constraints will be included into the new relation. - * (BuildDescForRelation takes care of the inherited defaults, but we - * have to copy inherited constraints here.) - */ - descriptor = BuildDescForRelation(schema); + /* Loop over all indices on the relation */ + indexoidlist = RelationGetIndexList(rel); - if (old_constraints != NIL) + foreach(indexoidscan, indexoidlist) { - ConstrCheck *check = (ConstrCheck *) palloc(length(old_constraints) * - sizeof(ConstrCheck)); - int ncheck = 0; - - foreach(listptr, old_constraints) - { - Constraint *cdef = (Constraint *) lfirst(listptr); + Oid indexoid = lfirsti(indexoidscan); + HeapTuple indexTuple; + Form_pg_index indexStruct; + int i; - if (cdef->contype != CONSTR_CHECK) - continue; + 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 (cdef->name != NULL) - { - for (i = 0; i < ncheck; i++) - { - if (strcmp(check[i].ccname, cdef->name) == 0) - elog(ERROR, "Duplicate CHECK constraint name: '%s'", - cdef->name); - } - check[ncheck].ccname = cdef->name; - } - else - { - check[ncheck].ccname = (char *) palloc(NAMEDATALEN); - snprintf(check[ncheck].ccname, NAMEDATALEN, "$%d", ncheck + 1); - } - Assert(cdef->raw_expr == NULL && cdef->cooked_expr != NULL); - check[ncheck].ccbin = pstrdup(cdef->cooked_expr); - ncheck++; - } - if (ncheck > 0) + /* If the index is not a primary key, skip the check */ + if (indexStruct->indisprimary) { - if (descriptor->constr == NULL) + /* + * 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++) { - descriptor->constr = (TupleConstr *) palloc(sizeof(TupleConstr)); - descriptor->constr->defval = NULL; - descriptor->constr->num_defval = 0; - descriptor->constr->has_not_null = false; + if (indexStruct->indkey[i] == attnum) + elog(ERROR, "ALTER TABLE: Attribute \"%s\" is in a primary key", colName); } - descriptor->constr->num_check = ncheck; - descriptor->constr->check = check; } - } - - relationId = heap_create_with_catalog(relname, - namespaceId, - descriptor, - relkind, - stmt->hasoids || parentHasOids, - allowSystemTableMods); - StoreCatalogInheritance(relationId, inheritOids); + ReleaseSysCache(indexTuple); + } - /* - * We must bump the command counter to make the newly-created relation - * tuple visible for opening. - */ - CommandCounterIncrement(); + freeList(indexoidlist); /* - * Open the new relation and acquire exclusive lock on it. This isn't - * really necessary for locking out other backends (since they can't - * see the new rel anyway until we commit), but it keeps the lock - * manager from complaining about deadlock risks. + * Okay, actually perform the catalog change */ - rel = heap_open(relationId, AccessExclusiveLock); + attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock); - /* - * Now add any newly specified column default values and CHECK - * constraints to the new relation. These are passed to us in the - * form of raw parsetrees; we need to transform them to executable - * expression trees before they can be added. The most convenient way - * to do that is to apply the parser's transformExpr routine, but - * transformExpr doesn't work unless we have a pre-existing relation. - * So, the transformation has to be postponed to this final step of - * CREATE TABLE. - * - * First, scan schema to find new column defaults. - */ - rawDefaults = NIL; - attnum = 0; + 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); - foreach(listptr, schema) - { - ColumnDef *colDef = lfirst(listptr); - RawColumnDefault *rawEnt; + ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = FALSE; - attnum++; + simple_heap_update(attr_rel, &tuple->t_self, tuple); - if (colDef->raw_default == NULL) - continue; - Assert(colDef->cooked_default == NULL); + /* keep the system catalog indices current */ + if (RelationGetForm(attr_rel)->relhasindex) + { + Relation idescs[Num_pg_attr_indices]; - rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault)); - rawEnt->attnum = attnum; - rawEnt->raw_default = colDef->raw_default; - rawDefaults = lappend(rawDefaults, rawEnt); + 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); } - /* - * Parse and add the defaults/constraints, if any. - */ - if (rawDefaults || stmt->constraints) - AddRelationRawConstraints(rel, rawDefaults, stmt->constraints); + heap_close(attr_rel, RowExclusiveLock); - /* - * Clean up. We keep lock on new relation (although it shouldn't be - * visible to anyone else anyway, until commit). - */ heap_close(rel, NoLock); - - return relationId; -} - -/* - * RemoveRelation - * Deletes a relation. - * - * Exceptions: - * BadArg if name is invalid. - * - * Note: - * If the relation has indices defined on it, then the index relations - * themselves will be destroyed, too. - */ -void -RemoveRelation(const RangeVar *relation) -{ - Oid relOid; - - relOid = RangeVarGetRelid(relation, false); - heap_drop_with_catalog(relOid, allowSystemTableMods); } /* - * TruncateRelation - * Removes all the rows from a relation - * - * Exceptions: - * BadArg if name is invalid - * - * Note: - * Rows are removed, indices are truncated and reconstructed. + * ALTER TABLE ALTER COLUMN SET NOT NULL */ void -TruncateRelation(const RangeVar *relation) +AlterTableAlterColumnSetNotNull(Oid myrelid, + bool inh, const char *colName) { Relation rel; - Oid relid; - - /* Grab exclusive lock in preparation for truncate */ - rel = heap_openrv(relation, AccessExclusiveLock); - relid = RelationGetRelid(rel); + HeapTuple tuple; + AttrNumber attnum; + Relation attr_rel; + HeapScanDesc scan; + TupleDesc tupdesc; - if (rel->rd_rel->relkind == RELKIND_SEQUENCE) - elog(ERROR, "TRUNCATE cannot be used on sequences. '%s' is a sequence", - RelationGetRelationName(rel)); + rel = heap_open(myrelid, AccessExclusiveLock); - if (rel->rd_rel->relkind == RELKIND_VIEW) - elog(ERROR, "TRUNCATE cannot be used on views. '%s' is a view", + 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, "TRUNCATE cannot be used on system tables. '%s' is a system table", + if (!allowSystemTableMods + && IsSystemRelation(rel)) + elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog", RelationGetRelationName(rel)); - if (!pg_class_ownercheck(relid, GetUserId())) - elog(ERROR, "you do not own relation \"%s\"", + if (!pg_class_ownercheck(myrelid, GetUserId())) + elog(ERROR, "ALTER TABLE: \"%s\": permission denied", RelationGetRelationName(rel)); - /* Keep the lock until transaction commit */ - heap_close(rel, NoLock); - - heap_truncate(relid); -} - - -/* - * MergeDomainAttributes - * Returns a new table schema with the constraints, types, and other - * attributes of domains resolved for fields using a domain as - * their type. - */ -static List * -MergeDomainAttributes(List *schema) -{ - List *entry; - /* - * Loop through the table elements supplied. These should - * never include inherited domains else they'll be - * double (or more) processed. + * Propagate to children if desired */ - foreach(entry, schema) + if (inh) { - ColumnDef *coldef = lfirst(entry); - HeapTuple tuple; - Form_pg_type typeTup; + List *child, + *children; - tuple = typenameType(coldef->typename); - typeTup = (Form_pg_type) GETSTRUCT(tuple); + /* this routine is actually in the planner */ + children = find_all_inheritors(myrelid); - if (typeTup->typtype == 'd') + /* + * 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) { - /* Force the column to have the correct typmod. */ - coldef->typename->typmod = typeTup->typtypmod; - /* XXX more to do here? */ - } - - /* Enforce type NOT NULL || column definition NOT NULL -> NOT NULL */ - /* Currently only used for domains, but could be valid for all */ - coldef->is_not_null |= typeTup->typnotnull; + Oid childrelid = lfirsti(child); - ReleaseSysCache(tuple); + if (childrelid == myrelid) + continue; + AlterTableAlterColumnSetNotNull(childrelid, + false, colName); + } } - return schema; -} - -/*---------- - * MergeAttributes - * Returns new schema given initial schema and superclasses. - * - * Input arguments: - * 'schema' is the column/attribute definition for the table. (It's a list - * of ColumnDef's.) It is destructively changed. - * 'supers' is a list of names (as RangeVar nodes) of parent relations. - * 'istemp' is TRUE if we are creating a temp relation. - * - * Output arguments: - * 'supOids' receives an integer list of the OIDs of the parent relations. - * 'supconstr' receives a list of constraints belonging to the parents, - * updated as necessary to be valid for the child. - * 'supHasOids' is set TRUE if any parent has OIDs, else it is set FALSE. - * - * Return value: - * Completed schema list. - * - * Notes: - * The order in which the attributes are inherited is very important. - * Intuitively, the inherited attributes should come first. If a table - * inherits from multiple parents, the order of those attributes are - * according to the order of the parents specified in CREATE TABLE. - * - * Here's an example: - * - * create table person (name text, age int4, location point); - * create table emp (salary int4, manager text) inherits(person); - * create table student (gpa float8) inherits (person); - * create table stud_emp (percent int4) inherits (emp, student); - * - * The order of the attributes of stud_emp is: - * - * person {1:name, 2:age, 3:location} - * / \ - * {6:gpa} student emp {4:salary, 5:manager} - * \ / - * stud_emp {7:percent} - * - * If the same attribute name appears multiple times, then it appears - * in the result table in the proper location for its first appearance. - * - * Constraints (including NOT NULL constraints) for the child table - * are the union of all relevant constraints, from both the child schema - * and parent tables. - * - * The default value for a child column is defined as: - * (1) If the child schema specifies a default, that value is used. - * (2) If neither the child nor any parent specifies a default, then - * the column will not have a default. - * (3) If conflicting defaults are inherited from different parents - * (and not overridden by the child), an error is raised. - * (4) Otherwise the inherited default is used. - * Rule (3) is new in Postgres 7.1; in earlier releases you got a - * rather arbitrary choice of which parent default to use. - *---------- - */ -static List * -MergeAttributes(List *schema, List *supers, bool istemp, - List **supOids, List **supconstr, bool *supHasOids) -{ - List *entry; - List *inhSchema = NIL; - List *parentOids = NIL; - List *constraints = NIL; - bool parentHasOids = false; - bool have_bogus_defaults = false; - char *bogus_marker = "Bogus!"; /* marks conflicting - * defaults */ - int child_attno; - + /* -= now do the thing on this relation =- */ + /* - * Check for duplicate names in the explicit list of attributes. - * - * Although we might consider merging such entries in the same way that - * we handle name conflicts for inherited attributes, it seems to make - * more sense to assume such conflicts are errors. + * get the number of the attribute */ - foreach(entry, schema) - { - ColumnDef *coldef = lfirst(entry); - List *rest; + 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); - foreach(rest, lnext(entry)) - { - ColumnDef *restdef = lfirst(rest); + attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum; + ReleaseSysCache(tuple); - if (strcmp(coldef->colname, restdef->colname) == 0) - elog(ERROR, "CREATE TABLE: attribute \"%s\" duplicated", - coldef->colname); - } - } + /* Prevent them from altering a system attribute */ + if (attnum < 0) + elog(ERROR, "ALTER TABLE: Cannot alter system attribute \"%s\"", + colName); /* - * Scan the parents left-to-right, and merge their attributes to form - * a list of inherited attributes (inhSchema). Also check to see if - * we need to inherit an OID column. + * Perform a scan to ensure that there are no NULL + * values already in the relation */ - child_attno = 0; - foreach(entry, supers) - { - RangeVar *parent = (RangeVar *) lfirst(entry); - Relation relation; - TupleDesc tupleDesc; - TupleConstr *constr; - AttrNumber *newattno; - AttrNumber parent_attno; + tupdesc = RelationGetDescr(rel); - relation = heap_openrv(parent, AccessShareLock); + scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL); - if (relation->rd_rel->relkind != RELKIND_RELATION) - elog(ERROR, "CREATE TABLE: inherited relation \"%s\" is not a table", - parent->relname); - /* Permanent rels cannot inherit from temporary ones */ - if (!istemp && isTempNamespace(RelationGetNamespace(relation))) - elog(ERROR, "CREATE TABLE: cannot inherit from temp relation \"%s\"", - parent->relname); + while (HeapTupleIsValid(tuple = heap_getnext(scan, 0))) + { + Datum d; + bool isnull; - /* - * We should have an UNDER permission flag for this, but for now, - * demand that creator of a child table own the parent. - */ - if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId())) - elog(ERROR, "you do not own table \"%s\"", - parent->relname); + d = heap_getattr(tuple, attnum, tupdesc, &isnull); - /* - * Reject duplications in the list of parents. - */ - if (intMember(RelationGetRelid(relation), parentOids)) - elog(ERROR, "CREATE TABLE: inherited relation \"%s\" duplicated", - parent->relname); + if (isnull) + elog(ERROR, "ALTER TABLE: Attribute \"%s\" contains NULL values", + colName); + } - parentOids = lappendi(parentOids, RelationGetRelid(relation)); - setRelhassubclassInRelation(RelationGetRelid(relation), true); + heap_endscan(scan); - parentHasOids |= relation->rd_rel->relhasoids; + /* + * Okay, actually perform the catalog change + */ + attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock); - tupleDesc = RelationGetDescr(relation); - constr = tupleDesc->constr; + 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); - /* - * newattno[] will contain the child-table attribute numbers for - * the attributes of this parent table. (They are not the same - * for parents after the first one.) - */ - newattno = (AttrNumber *) palloc(tupleDesc->natts * sizeof(AttrNumber)); + ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = TRUE; - for (parent_attno = 1; parent_attno <= tupleDesc->natts; - parent_attno++) - { - Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1]; - char *attributeName = NameStr(attribute->attname); - int exist_attno; - ColumnDef *def; - TypeName *typename; + simple_heap_update(attr_rel, &tuple->t_self, tuple); - /* - * Does it conflict with some previously inherited column? - */ - exist_attno = findAttrByName(attributeName, inhSchema); - if (exist_attno > 0) - { - /* - * Yes, try to merge the two column definitions. They must - * have the same type and typmod. - */ - elog(NOTICE, "CREATE TABLE: merging multiple inherited definitions of attribute \"%s\"", - attributeName); - def = (ColumnDef *) nth(exist_attno - 1, inhSchema); - if (typenameTypeId(def->typename) != attribute->atttypid || - def->typename->typmod != attribute->atttypmod) - elog(ERROR, "CREATE TABLE: inherited attribute \"%s\" type conflict (%s and %s)", - attributeName, - TypeNameToString(def->typename), - typeidTypeName(attribute->atttypid)); - /* Merge of NOT NULL constraints = OR 'em together */ - def->is_not_null |= attribute->attnotnull; - /* Default and other constraints are handled below */ - newattno[parent_attno - 1] = exist_attno; - } - else - { - /* - * No, create a new inherited column - */ - def = makeNode(ColumnDef); - def->colname = pstrdup(attributeName); - typename = makeNode(TypeName); - typename->typeid = attribute->atttypid; - typename->typmod = attribute->atttypmod; - def->typename = typename; - def->is_not_null = attribute->attnotnull; - def->raw_default = NULL; - def->cooked_default = NULL; - def->constraints = NIL; - inhSchema = lappend(inhSchema, def); - newattno[parent_attno - 1] = ++child_attno; - } + /* keep the system catalog indices current */ + if (RelationGetForm(attr_rel)->relhasindex) + { + Relation idescs[Num_pg_attr_indices]; - /* - * Copy default if any - */ - if (attribute->atthasdef) - { - char *this_default = NULL; - AttrDefault *attrdef; - int i; + 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); + } - /* Find default in constraint structure */ - Assert(constr != NULL); - attrdef = constr->defval; - for (i = 0; i < constr->num_defval; i++) - { - if (attrdef[i].adnum == parent_attno) - { - this_default = attrdef[i].adbin; - break; - } - } - Assert(this_default != NULL); + heap_close(attr_rel, RowExclusiveLock); - /* - * If default expr could contain any vars, we'd need to - * fix 'em, but it can't; so default is ready to apply to - * child. - * - * If we already had a default from some prior parent, check - * to see if they are the same. If so, no problem; if - * not, mark the column as having a bogus default. Below, - * we will complain if the bogus default isn't overridden - * by the child schema. - */ - Assert(def->raw_default == NULL); - if (def->cooked_default == NULL) - def->cooked_default = pstrdup(this_default); - else if (strcmp(def->cooked_default, this_default) != 0) - { - def->cooked_default = bogus_marker; - have_bogus_defaults = true; - } - } - } + heap_close(rel, NoLock); +} - /* - * Now copy the constraints of this parent, adjusting attnos using - * the completed newattno[] map - */ - if (constr && constr->num_check > 0) - { - ConstrCheck *check = constr->check; - int i; - for (i = 0; i < constr->num_check; i++) - { - Constraint *cdef = makeNode(Constraint); - Node *expr; +/* + * ALTER TABLE ALTER COLUMN SET/DROP DEFAULT + */ +void +AlterTableAlterColumnDefault(Oid myrelid, + bool inh, const char *colName, + Node *newDefault) +{ + Relation rel; + HeapTuple tuple; + AttrNumber attnum; - cdef->contype = CONSTR_CHECK; - if (check[i].ccname[0] == '$') - cdef->name = NULL; - else - cdef->name = pstrdup(check[i].ccname); - cdef->raw_expr = NULL; - /* adjust varattnos of ccbin here */ - expr = stringToNode(check[i].ccbin); - change_varattnos_of_a_node(expr, newattno); - cdef->cooked_expr = nodeToString(expr); - constraints = lappend(constraints, cdef); - } - } + rel = heap_open(myrelid, AccessExclusiveLock); - pfree(newattno); + /* + * 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)); - /* - * Close the parent rel, but keep our AccessShareLock on it until - * xact commit. That will prevent someone else from deleting or - * ALTERing the parent before the child is committed. - */ - heap_close(relation, NoLock); - } + if (!pg_class_ownercheck(myrelid, GetUserId())) + elog(ERROR, "ALTER TABLE: \"%s\": permission denied", + RelationGetRelationName(rel)); /* - * If we had no inherited attributes, the result schema is just the - * explicitly declared columns. Otherwise, we need to merge the - * declared columns into the inherited schema list. + * Propagate to children if desired */ - if (inhSchema != NIL) + if (inh) { - foreach(entry, schema) - { - ColumnDef *newdef = lfirst(entry); - char *attributeName = newdef->colname; - int exist_attno; + List *child, + *children; - /* - * Does it conflict with some previously inherited column? - */ - exist_attno = findAttrByName(attributeName, inhSchema); - if (exist_attno > 0) - { - ColumnDef *def; + /* this routine is actually in the planner */ + children = find_all_inheritors(myrelid); - /* - * Yes, try to merge the two column definitions. They must - * have the same type and typmod. - */ - elog(NOTICE, "CREATE TABLE: merging attribute \"%s\" with inherited definition", - attributeName); - def = (ColumnDef *) nth(exist_attno - 1, inhSchema); - if (typenameTypeId(def->typename) != typenameTypeId(newdef->typename) || - def->typename->typmod != newdef->typename->typmod) - elog(ERROR, "CREATE TABLE: attribute \"%s\" type conflict (%s and %s)", - attributeName, - TypeNameToString(def->typename), - TypeNameToString(newdef->typename)); - /* Merge of NOT NULL constraints = OR 'em together */ - def->is_not_null |= newdef->is_not_null; - /* If new def has a default, override previous default */ - if (newdef->raw_default != NULL) - { - def->raw_default = newdef->raw_default; - def->cooked_default = newdef->cooked_default; - } - } - else - { - /* - * No, attach new column to result schema - */ - inhSchema = lappend(inhSchema, newdef); - } - } + /* + * 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); - schema = inhSchema; + if (childrelid == myrelid) + continue; + AlterTableAlterColumnDefault(childrelid, + false, colName, newDefault); + } } + /* -= now do the thing on this relation =- */ + /* - * If we found any conflicting parent default values, check to make - * sure they were overridden by the child. + * get the number of the attribute */ - if (have_bogus_defaults) + 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) { - foreach(entry, schema) - { - ColumnDef *def = lfirst(entry); + /* SET DEFAULT */ + RawColumnDefault *rawEnt; - if (def->cooked_default == bogus_marker) - elog(ERROR, "CREATE TABLE: attribute \"%s\" inherits conflicting default values" - "\n\tTo resolve the conflict, specify a default explicitly", - def->colname); - } - } + /* Get rid of the old one first */ + drop_default(myrelid, attnum); - *supOids = parentOids; - *supconstr = constraints; - *supHasOids = parentHasOids; - return schema; -} + rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault)); + rawEnt->attnum = attnum; + rawEnt->raw_default = newDefault; -/* - * complementary static functions for MergeAttributes(). - * - * Varattnos of pg_relcheck.rcbin must be rewritten when subclasses inherit - * constraints from parent classes, since the inherited attributes could - * be given different column numbers in multiple-inheritance cases. - * - * Note that the passed node tree is modified in place! - */ -static bool -change_varattnos_walker(Node *node, const AttrNumber *newattno) -{ - if (node == NULL) - return false; - if (IsA(node, Var)) + /* + * 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 { - Var *var = (Var *) node; + /* DROP DEFAULT */ + Relation attr_rel; - if (var->varlevelsup == 0 && var->varno == 1 && - var->varattno > 0) + /* 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) { - /* - * ??? the following may be a problem when the node is - * multiply referenced though stringToNode() doesn't create - * such a node currently. - */ - Assert(newattno[var->varattno - 1] > 0); - var->varattno = newattno[var->varattno - 1]; + 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); } - return false; + + heap_close(attr_rel, RowExclusiveLock); + + /* get rid of actual default definition in pg_attrdef */ + drop_default(myrelid, attnum); } - return expression_tree_walker(node, change_varattnos_walker, - (void *) newattno); -} -static bool -change_varattnos_of_a_node(Node *node, const AttrNumber *newattno) -{ - return change_varattnos_walker(node, newattno); + heap_close(rel, NoLock); } -/* - * StoreCatalogInheritance - * Updates the system catalogs with proper inheritance information. - * - * supers is an integer list of the OIDs of the new relation's direct - * ancestors. NB: it is destructively changed to include indirect ancestors. - */ + static void -StoreCatalogInheritance(Oid relationId, List *supers) +drop_default(Oid relid, int16 attnum) { - Relation relation; - TupleDesc desc; - int16 seqNumber; - List *entry; + ScanKeyData scankeys[2]; + HeapScanDesc scan; + Relation attrdef_rel; HeapTuple tuple; - /* - * sanity checks - */ - AssertArg(OidIsValid(relationId)); + 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)); - if (supers == NIL) - return; + scan = heap_beginscan(attrdef_rel, false, SnapshotNow, 2, scankeys); - /* - * Catalog INHERITS information using direct ancestors only. - */ - relation = heap_openr(InheritsRelationName, RowExclusiveLock); - desc = RelationGetDescr(relation); + if (HeapTupleIsValid(tuple = heap_getnext(scan, 0))) + simple_heap_delete(attrdef_rel, &tuple->t_self); - seqNumber = 1; - foreach(entry, supers) - { - Oid entryOid = lfirsti(entry); - Datum datum[Natts_pg_inherits]; - char nullarr[Natts_pg_inherits]; + heap_endscan(scan); - datum[0] = ObjectIdGetDatum(relationId); /* inhrel */ - datum[1] = ObjectIdGetDatum(entryOid); /* inhparent */ - datum[2] = Int16GetDatum(seqNumber); /* inhseqno */ + heap_close(attrdef_rel, NoLock); +} - nullarr[0] = ' '; - nullarr[1] = ' '; - nullarr[2] = ' '; - tuple = heap_formtuple(desc, datum, nullarr); +/* + * 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); - heap_insert(relation, tuple); + if (rel->rd_rel->relkind != RELKIND_RELATION) + elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", + RelationGetRelationName(rel)); - if (RelationGetForm(relation)->relhasindex) - { - Relation idescs[Num_pg_inherits_indices]; + /* + * 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)); - CatalogOpenIndices(Num_pg_inherits_indices, Name_pg_inherits_indices, idescs); - CatalogIndexInsert(idescs, Num_pg_inherits_indices, relation, tuple); - CatalogCloseIndices(Num_pg_inherits_indices, idescs); - } + if (!pg_class_ownercheck(myrelid, GetUserId())) + elog(ERROR, "ALTER TABLE: \"%s\": permission denied", + RelationGetRelationName(rel)); - heap_freetuple(tuple); + /* + * Check the supplied parameters before anything else + */ + if (*flagType == 'S') + { + /* STATISTICS */ + Assert(IsA(flagValue, Integer)); + newtarget = intVal(flagValue); - seqNumber += 1; + /* + * 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; - heap_close(relation, RowExclusiveLock); + Assert(IsA(flagValue, String)); + storagemode = strVal(flagValue); - /* ---------------- - * Expand supers list to include indirect ancestors as well. - * - * Algorithm: - * 0. begin with list of direct superclasses. - * 1. append after each relationId, its superclasses, recursively. - * 2. remove all but last of duplicates. - * ---------------- - */ + 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); + } /* - * 1. append after each relationId, its superclasses, recursively. + * Propagate to children if desired */ - foreach(entry, supers) + if (inh) { - HeapTuple tuple; - Oid id; - int16 number; - List *next; - List *current; + List *child, + *children; - id = (Oid) lfirsti(entry); - current = entry; - next = lnext(entry); + /* this routine is actually in the planner */ + children = find_all_inheritors(myrelid); - for (number = 1;; number += 1) + /* + * 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) { - tuple = SearchSysCache(INHRELID, - ObjectIdGetDatum(id), - Int16GetDatum(number), - 0, 0); - if (!HeapTupleIsValid(tuple)) - break; - - lnext(current) = lconsi(((Form_pg_inherits) - GETSTRUCT(tuple))->inhparent, - NIL); - - ReleaseSysCache(tuple); + Oid childrelid = lfirsti(child); - current = lnext(current); + if (childrelid == myrelid) + continue; + AlterTableAlterColumnFlags(childrelid, + false, colName, flagValue, flagType); } - lnext(current) = next; } + /* -= 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); /* - * 2. remove all but last of duplicates. + * Now change the appropriate field */ - foreach(entry, supers) + if (*flagType == 'S') + attrtuple->attstattarget = newtarget; + else if (*flagType == 'M') { - Oid thisone; - bool found; - List *rest; - -again: - thisone = lfirsti(entry); - found = false; - foreach(rest, lnext(entry)) - { - if (thisone == lfirsti(rest)) - { - found = true; - break; - } - } - if (found) - { - /* - * found a later duplicate, so remove this entry. - */ - lfirsti(entry) = lfirsti(lnext(entry)); - lnext(entry) = lnext(lnext(entry)); - - goto again; - } + /* + * 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)); } -} -/* - * Look for an existing schema entry with the given name. - * - * Returns the index (starting with 1) if attribute already exists in schema, - * 0 if it doesn't. - */ -static int -findAttrByName(const char *attributeName, List *schema) -{ - List *s; - int i = 0; + simple_heap_update(attrelation, &tuple->t_self, tuple); - foreach(s, schema) + /* keep system catalog indices current */ { - ColumnDef *def = lfirst(s); + Relation irelations[Num_pg_attr_indices]; - ++i; - if (strcmp(attributeName, def->colname) == 0) - return i; + CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, irelations); + CatalogIndexInsert(irelations, Num_pg_attr_indices, attrelation, tuple); + CatalogCloseIndices(Num_pg_attr_indices, irelations); } - return 0; + + heap_freetuple(tuple); + heap_close(attrelation, NoLock); + heap_close(rel, NoLock); /* close rel, but keep lock! */ } + /* - * Update a relation's pg_class.relhassubclass entry to the given value + * ALTER TABLE DROP COLUMN */ -static void -setRelhassubclassInRelation(Oid relationId, bool relhassubclass) +void +AlterTableDropColumn(Oid myrelid, + bool inh, const char *colName, + int behavior) { - Relation relationRelation; - HeapTuple tuple; - Relation idescs[Num_pg_class_indices]; - - /* - * Fetch a modifiable copy of the tuple, modify it, update pg_class. - */ - relationRelation = heap_openr(RelationRelationName, RowExclusiveLock); - tuple = SearchSysCacheCopy(RELOID, - ObjectIdGetDatum(relationId), - 0, 0, 0); - if (!HeapTupleIsValid(tuple)) - elog(ERROR, "setRelhassubclassInRelation: cache lookup failed for relation %u", relationId); - - ((Form_pg_class) GETSTRUCT(tuple))->relhassubclass = relhassubclass; - simple_heap_update(relationRelation, &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, relationRelation, tuple); - CatalogCloseIndices(Num_pg_class_indices, idescs); - - heap_freetuple(tuple); - heap_close(relationRelation, RowExclusiveLock); + elog(ERROR, "ALTER TABLE / DROP COLUMN is not implemented"); } /* - * renameatt - changes the name of a attribute in a relation - * - * Attname attribute is changed in attribute catalog. - * No record of the previous attname is kept (correct?). - * - * get proper relrelation from relation catalog (if not arg) - * scan attribute catalog - * for name conflict (within rel) - * for original attribute (if not arg) - * modify attname in attribute tuple - * insert modified attribute in attribute catalog - * delete original attribute from attribute catalog + * ALTER TABLE ADD CONSTRAINT */ void -renameatt(Oid relid, - const char *oldattname, - const char *newattname, - bool recurse) -{ - Relation targetrelation; - Relation attrelation; - HeapTuple atttup; - List *indexoidlist; - List *indexoidscan; +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. */ - targetrelation = heap_open(relid, AccessExclusiveLock); + rel = heap_open(myrelid, AccessExclusiveLock); - /* - * 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(targetrelation)) - elog(ERROR, "renameatt: class \"%s\" is a system catalog", - RelationGetRelationName(targetrelation)); - if (!pg_class_ownercheck(relid, GetUserId())) - elog(ERROR, "renameatt: you do not own class \"%s\"", - RelationGetRelationName(targetrelation)); + if (rel->rd_rel->relkind != RELKIND_RELATION) + elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", + RelationGetRelationName(rel)); - /* - * if the 'recurse' flag is set then we are supposed to rename this - * attribute in all classes that inherit from 'relname' (as well as in - * 'relname'). - * - * any permissions or problems with duplicate attributes will cause the - * whole transaction to abort, which is what we want -- all or - * nothing. - */ - if (recurse) + 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(relid); + children = find_all_inheritors(myrelid); /* * find_all_inheritors does the recursive search of the @@ -2625,571 +2390,686 @@ renameatt(Oid relid, { Oid childrelid = lfirsti(child); - if (childrelid == relid) + if (childrelid == myrelid) continue; - /* note we need not recurse again! */ - renameatt(childrelid, oldattname, newattname, false); + AlterTableAddConstraint(childrelid, false, newConstraints); } } - attrelation = heap_openr(AttributeRelationName, RowExclusiveLock); + foreach(listptr, newConstraints) + { + Node *newConstraint = lfirst(listptr); - atttup = SearchSysCacheCopy(ATTNAME, - ObjectIdGetDatum(relid), - PointerGetDatum(oldattname), - 0, 0); - if (!HeapTupleIsValid(atttup)) - elog(ERROR, "renameatt: attribute \"%s\" does not exist", oldattname); + switch (nodeTag(newConstraint)) + { + case T_Constraint: + { + Constraint *constr = (Constraint *) newConstraint; - if (((Form_pg_attribute) GETSTRUCT(atttup))->attnum < 0) - elog(ERROR, "renameatt: system attribute \"%s\" not renamed", oldattname); + /* + * 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; - /* should not already exist */ - if (SearchSysCacheExists(ATTNAME, - ObjectIdGetDatum(relid), - PointerGetDatum(newattname), - 0, 0)) - elog(ERROR, "renameatt: attribute \"%s\" exists", newattname); + if (constr->name) + name = constr->name; + else + name = "<unnamed>"; + + /* + * 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 = "<unknown>"; + 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); - namestrcpy(&(((Form_pg_attribute) GETSTRUCT(atttup))->attname), - newattname); + trig.tgargs[count] = fk_at->name; + count += 2; + } + count = 5; + foreach(list, fkconstraint->pk_attrs) + { + Ident *pk_at = lfirst(list); - simple_heap_update(attrelation, &atttup->t_self, atttup); + trig.tgargs[count] = pk_at->name; + count += 2; + } + trig.tgnargs = count - 1; - /* keep system catalog indices current */ - { - Relation irelations[Num_pg_attr_indices]; + scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL); - CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, irelations); - CatalogIndexInsert(irelations, Num_pg_attr_indices, attrelation, atttup); - CatalogCloseIndices(Num_pg_attr_indices, irelations); - } + while (HeapTupleIsValid(tuple = heap_getnext(scan, 0))) + { + /* Make a call to the check function */ - heap_freetuple(atttup); + /* + * No parameters are passed, but we do set a + * context + */ + FunctionCallInfoData fcinfo; + TriggerData trigdata; - /* - * Update column names of indexes that refer to the column being - * renamed. - */ - indexoidlist = RelationGetIndexList(targetrelation); + MemSet(&fcinfo, 0, sizeof(fcinfo)); - foreach(indexoidscan, indexoidlist) - { - Oid indexoid = lfirsti(indexoidscan); - HeapTuple indextup; + /* + * We assume RI_FKey_check_ins won't look at + * flinfo... + */ - /* - * First check to see if index is a functional index. If so, its - * column name is a function name and shouldn't be renamed here. - */ - indextup = SearchSysCache(INDEXRELID, - ObjectIdGetDatum(indexoid), - 0, 0, 0); - if (!HeapTupleIsValid(indextup)) - elog(ERROR, "renameatt: can't find index id %u", indexoid); - if (OidIsValid(((Form_pg_index) GETSTRUCT(indextup))->indproc)) - { - ReleaseSysCache(indextup); - continue; - } - ReleaseSysCache(indextup); + 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; - /* - * Okay, look to see if any column name of the index matches the - * old attribute name. - */ - atttup = SearchSysCacheCopy(ATTNAME, - ObjectIdGetDatum(indexoid), - PointerGetDatum(oldattname), - 0, 0); - if (!HeapTupleIsValid(atttup)) - continue; /* Nope, so ignore it */ + fcinfo.context = (Node *) &trigdata; - /* - * Update the (copied) attribute tuple. - */ - namestrcpy(&(((Form_pg_attribute) GETSTRUCT(atttup))->attname), - newattname); + RI_FKey_check_ins(&fcinfo); + } + heap_endscan(scan); - simple_heap_update(attrelation, &atttup->t_self, atttup); + pfree(trig.tgargs); - /* keep system catalog indices current */ - { - Relation irelations[Num_pg_attr_indices]; + heap_close(pkrel, NoLock); - CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, irelations); - CatalogIndexInsert(irelations, Num_pg_attr_indices, attrelation, atttup); - CatalogCloseIndices(Num_pg_attr_indices, irelations); + break; + } + default: + elog(ERROR, "ALTER TABLE / ADD CONSTRAINT unable to determine type of constraint passed"); } - heap_freetuple(atttup); - } - - freeList(indexoidlist); - - heap_close(attrelation, RowExclusiveLock); - - /* - * Update att name in any RI triggers associated with the relation. - */ - if (targetrelation->rd_rel->reltriggers > 0) - { - /* update tgargs column reference where att is primary key */ - update_ri_trigger_args(RelationGetRelid(targetrelation), - oldattname, newattname, - false, false); - /* update tgargs column reference where att is foreign key */ - update_ri_trigger_args(RelationGetRelid(targetrelation), - oldattname, newattname, - true, false); } - heap_close(targetrelation, NoLock); /* close rel but keep lock! */ + /* Close rel, but keep lock till commit */ + heap_close(rel, NoLock); } + /* - * renamerel - change the name of a relation - * - * XXX - When renaming sequences, we don't bother to modify the - * sequence name that is stored within the sequence itself - * (this would cause problems with MVCC). In the future, - * the sequence name should probably be removed from the - * sequence, AFAIK there's no need for it to be there. + * 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 -renamerel(Oid relid, const char *newrelname) +AlterTableDropConstraint(Oid myrelid, + bool inh, const char *constrName, + int behavior) { - Relation targetrelation; - Relation relrelation; /* for RELATION relation */ - HeapTuple reltup; - Oid namespaceId; - char *oldrelname; - char relkind; - bool relhastriggers; - Relation irelations[Num_pg_class_indices]; + Relation rel; + int deleted; /* - * Grab an exclusive lock on the target table or index, which we will - * NOT release until end of transaction. + * We don't support CASCADE yet - in fact, RESTRICT doesn't work to + * the spec either! */ - targetrelation = relation_open(relid, AccessExclusiveLock); - - oldrelname = pstrdup(RelationGetRelationName(targetrelation)); - namespaceId = RelationGetNamespace(targetrelation); - - /* Validity checks */ - if (!allowSystemTableMods && - IsSystemRelation(targetrelation)) - elog(ERROR, "renamerel: system relation \"%s\" may not be renamed", - oldrelname); - - relkind = targetrelation->rd_rel->relkind; - relhastriggers = (targetrelation->rd_rel->reltriggers > 0); + if (behavior == CASCADE) + elog(ERROR, "ALTER TABLE / DROP CONSTRAINT does not support the CASCADE keyword"); /* - * Find relation's pg_class tuple, and make sure newrelname isn't in - * use. + * Acquire an exclusive lock on the target relation for the duration + * of the operation. */ - relrelation = heap_openr(RelationRelationName, RowExclusiveLock); + rel = heap_open(myrelid, AccessExclusiveLock); - reltup = SearchSysCacheCopy(RELOID, - PointerGetDatum(relid), - 0, 0, 0); - if (!HeapTupleIsValid(reltup)) - elog(ERROR, "renamerel: relation \"%s\" does not exist", - oldrelname); + /* 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 (get_relname_relid(newrelname, namespaceId) != InvalidOid) - elog(ERROR, "renamerel: relation \"%s\" exists", newrelname); + 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)); /* - * Update pg_class tuple with new relname. (Scribbling on reltup is - * OK because it's a copy...) + * 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. */ - namestrcpy(&(((Form_pg_class) GETSTRUCT(reltup))->relname), newrelname); - - simple_heap_update(relrelation, &reltup->t_self, reltup); - - /* keep the system catalog indices current */ - CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, irelations); - CatalogIndexInsert(irelations, Num_pg_class_indices, relrelation, reltup); - CatalogCloseIndices(Num_pg_class_indices, irelations); - heap_close(relrelation, NoLock); - heap_freetuple(reltup); + deleted = 0; /* - * Also rename the associated type, if any. + * First, we remove all CHECK constraints with the given name */ - if (relkind != RELKIND_INDEX) - TypeRename(oldrelname, namespaceId, newrelname); - /* - * Update rel name in any RI triggers associated with the relation. - */ - if (relhastriggers) - { - /* update tgargs where relname is primary key */ - update_ri_trigger_args(relid, - oldrelname, - newrelname, - false, true); - /* update tgargs where relname is foreign key */ - update_ri_trigger_args(relid, - oldrelname, - newrelname, - true, true); - } + deleted += RemoveCheckConstraint(rel, constrName, inh); /* - * Close rel, but keep exclusive lock! + * Now we remove NULL, UNIQUE, PRIMARY KEY and FOREIGN KEY + * constraints. + * + * Unimplemented. */ - 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 + /* 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 */ -extern void renametrig(Oid relid, - const char *oldname, - const char *newname) +void +AlterTableOwner(Oid relationOid, int32 newOwnerSysId) { - Relation targetrel; - Relation tgrel; - HeapTuple tuple; - SysScanDesc tgscan; - ScanKeyData key; - bool found = FALSE; - Relation idescs[Num_pg_trigger_indices]; + Relation target_rel; + Relation class_rel; + HeapTuple tuple; + Relation idescs[Num_pg_class_indices]; + Form_pg_class tuple_class; - /* - * Grab an exclusive lock on the target table, which we will NOT - * release until end of transaction. - */ - targetrel = heap_open(relid, AccessExclusiveLock); + /* Get exclusive lock till end of transaction on the target table */ + target_rel = heap_open(relationOid, 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); + /* 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); /* - * First pass -- look for name conflict + * Okay, this is a valid tuple: change its ownership and + * write to the heap. */ - 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); + tuple_class->relowner = newOwnerSysId; + simple_heap_update(class_rel, &tuple->t_self, tuple); - if (namestrcmp(&(pg_trigger->tgname), newname) == 0) - elog(ERROR, "renametrig: trigger %s already defined on relation %s", - newname, RelationGetRelationName(targetrel)); - } - systable_endscan(tgscan); + /* 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); /* - * Second pass -- look for trigger existing with oldname and update + * 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) */ - 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))) + if (tuple_class->relkind == RELKIND_RELATION || + tuple_class->relkind == RELKIND_TOASTVALUE) { - Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple); + List *index_oid_list, *i; - 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); + /* Find all the indexes belonging to this relation */ + index_oid_list = RelationGetIndexList(target_rel); - /* - * 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); + /* For each index, recursively change its ownership */ + foreach(i, index_oid_list) + { + AlterTableOwner(lfirsti(i), newOwnerSysId); + } - /* - * 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); + freeList(index_oid_list); + } - found = TRUE; - break; + 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); } } - 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); + heap_freetuple(tuple); + heap_close(class_rel, RowExclusiveLock); + heap_close(target_rel, NoLock); } - -/* - * Given a trigger function OID, determine whether it is an RI trigger, - * and if so whether it is attached to PK or FK relation. - * - * XXX this probably doesn't belong here; should be exported by - * ri_triggers.c - */ -static int -ri_trigger_type(Oid tgfoid) +static void +CheckTupleType(Form_pg_class tuple_class) { - switch (tgfoid) + switch (tuple_class->relkind) { - case F_RI_FKEY_CASCADE_DEL: - case F_RI_FKEY_CASCADE_UPD: - case F_RI_FKEY_RESTRICT_DEL: - case F_RI_FKEY_RESTRICT_UPD: - case F_RI_FKEY_SETNULL_DEL: - case F_RI_FKEY_SETNULL_UPD: - case F_RI_FKEY_SETDEFAULT_DEL: - case F_RI_FKEY_SETDEFAULT_UPD: - case F_RI_FKEY_NOACTION_DEL: - case F_RI_FKEY_NOACTION_UPD: - return RI_TRIGGER_PK; - - case F_RI_FKEY_CHECK_INS: - case F_RI_FKEY_CHECK_UPD: - return RI_TRIGGER_FK; + 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)); } - - return RI_TRIGGER_NONE; } /* - * Scan pg_trigger for RI triggers that are on the specified relation - * (if fk_scan is false) or have it as the tgconstrrel (if fk_scan - * is true). Update RI trigger args fields matching oldname to contain - * newname instead. If update_relname is true, examine the relname - * fields; otherwise examine the attname fields. + * ALTER TABLE CREATE TOAST TABLE */ -static void -update_ri_trigger_args(Oid relid, - const char *oldname, - const char *newname, - bool fk_scan, - bool update_relname) +void +AlterTableCreateToastTable(Oid relOid, bool silent) { - Relation tgrel; - Relation irel; - ScanKeyData skey[1]; - IndexScanDesc idxtgscan; - RetrieveIndexResult idxres; - Datum values[Natts_pg_trigger]; - char nulls[Natts_pg_trigger]; - char replaces[Natts_pg_trigger]; + 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]; - tgrel = heap_openr(TriggerRelationName, RowExclusiveLock); - if (fk_scan) - irel = index_openr(TriggerConstrRelidIndex); - else - irel = index_openr(TriggerRelidNameIndex); + /* + * Grab an exclusive lock on the target table, which we will NOT + * release until end of transaction. + */ + rel = heap_open(relOid, AccessExclusiveLock); - ScanKeyEntryInitialize(&skey[0], 0x0, - 1, /* column 1 of index in either case */ - F_OIDEQ, - ObjectIdGetDatum(relid)); - idxtgscan = index_beginscan(irel, false, 1, skey); + if (rel->rd_rel->relkind != RELKIND_RELATION) + elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", + RelationGetRelationName(rel)); - while ((idxres = index_getnext(idxtgscan, ForwardScanDirection)) != NULL) - { - HeapTupleData tupledata; - Buffer buffer; - HeapTuple tuple; - Form_pg_trigger pg_trigger; - bytea *val; - bytea *newtgargs; - bool isnull; - int tg_type; - bool examine_pk; - bool changed; - int tgnargs; - int i; - int newlen; - const char *arga[RI_MAX_ARGUMENTS]; - const char *argp; + if (!pg_class_ownercheck(relOid, GetUserId())) + elog(ERROR, "ALTER TABLE: \"%s\": permission denied", + RelationGetRelationName(rel)); - tupledata.t_self = idxres->heap_iptr; - heap_fetch(tgrel, SnapshotNow, &tupledata, &buffer, idxtgscan); - pfree(idxres); - if (!tupledata.t_data) - continue; - tuple = &tupledata; - pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple); - tg_type = ri_trigger_type(pg_trigger->tgfoid); - if (tg_type == RI_TRIGGER_NONE) - { - /* Not an RI trigger, forget it */ - ReleaseBuffer(buffer); - continue; - } + /* + * lock the pg_class tuple for update (is that really needed?) + */ + class_rel = heap_openr(RelationRelationName, RowExclusiveLock); - /* - * It is an RI trigger, so parse the tgargs bytea. - * - * NB: we assume the field will never be compressed or moved out of - * line; so does trigger.c ... - */ - tgnargs = pg_trigger->tgnargs; - val = (bytea *) fastgetattr(tuple, - Anum_pg_trigger_tgargs, - tgrel->rd_att, &isnull); - if (isnull || tgnargs < RI_FIRST_ATTNAME_ARGNO || - tgnargs > RI_MAX_ARGUMENTS) - { - /* This probably shouldn't happen, but ignore busted triggers */ - ReleaseBuffer(buffer); - continue; - } - argp = (const char *) VARDATA(val); - for (i = 0; i < tgnargs; i++) + 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) { - arga[i] = argp; - argp += strlen(argp) + 1; + heap_close(rel, NoLock); + heap_close(class_rel, NoLock); + heap_freetuple(reltup); + return; } - /* - * Figure out which item(s) to look at. If the trigger is - * primary-key type and attached to my rel, I should look at the - * PK fields; if it is foreign-key type and attached to my rel, I - * should look at the FK fields. But the opposite rule holds when - * examining triggers found by tgconstrrel search. - */ - examine_pk = (tg_type == RI_TRIGGER_PK) == (!fk_scan); + elog(ERROR, "ALTER TABLE: relation \"%s\" already has a toast table", + RelationGetRelationName(rel)); + } - changed = false; - if (update_relname) - { - /* Change the relname if needed */ - i = examine_pk ? RI_PK_RELNAME_ARGNO : RI_FK_RELNAME_ARGNO; - if (strcmp(arga[i], oldname) == 0) - { - arga[i] = newname; - changed = true; - } - } - else + /* + * Check to see whether the table actually needs a TOAST table. + */ + if (!needs_toast_table(rel)) + { + if (silent) { - /* Change attname(s) if needed */ - i = examine_pk ? RI_FIRST_ATTNAME_ARGNO + RI_KEYPAIR_PK_IDX : - RI_FIRST_ATTNAME_ARGNO + RI_KEYPAIR_FK_IDX; - for (; i < tgnargs; i += 2) - { - if (strcmp(arga[i], oldname) == 0) - { - arga[i] = newname; - changed = true; - } - } + heap_close(rel, NoLock); + heap_close(class_rel, NoLock); + heap_freetuple(reltup); + return; } - if (!changed) - { - /* Don't need to update this tuple */ - ReleaseBuffer(buffer); - continue; - } + elog(ERROR, "ALTER TABLE: relation \"%s\" does not need a toast table", + RelationGetRelationName(rel)); + } - /* - * Construct modified tgargs bytea. - */ - newlen = VARHDRSZ; - for (i = 0; i < tgnargs; i++) - newlen += strlen(arga[i]) + 1; - newtgargs = (bytea *) palloc(newlen); - VARATT_SIZEP(newtgargs) = newlen; - newlen = VARHDRSZ; - for (i = 0; i < tgnargs; i++) - { - strcpy(((char *) newtgargs) + newlen, arga[i]); - newlen += strlen(arga[i]) + 1; - } + /* + * Create the toast table and its index + */ + sprintf(toast_relname, "pg_toast_%u", relOid); + sprintf(toast_idxname, "pg_toast_%u_index", relOid); - /* - * Build modified tuple. - */ - for (i = 0; i < Natts_pg_trigger; i++) - { - values[i] = (Datum) 0; - replaces[i] = ' '; - nulls[i] = ' '; - } - values[Anum_pg_trigger_tgargs - 1] = PointerGetDatum(newtgargs); - replaces[Anum_pg_trigger_tgargs - 1] = 'r'; + /* 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); - tuple = heap_modifytuple(tuple, tgrel, values, nulls, replaces); + /* + * 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'; - /* - * Now we can release hold on original tuple. - */ - ReleaseBuffer(buffer); + /* + * 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); - /* - * Update pg_trigger and its indexes - */ - simple_heap_update(tgrel, &tuple->t_self, tuple); + /* make the toast relation visible, else index creation will fail */ + CommandCounterIncrement(); - { - Relation irelations[Num_pg_attr_indices]; + /* + * 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. + */ - CatalogOpenIndices(Num_pg_trigger_indices, Name_pg_trigger_indices, irelations); - CatalogIndexInsert(irelations, Num_pg_trigger_indices, tgrel, tuple); - CatalogCloseIndices(Num_pg_trigger_indices, irelations); - } + 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; - /* free up our scratch memory */ - pfree(newtgargs); - heap_freetuple(tuple); - } + classObjectId[0] = OID_BTREE_OPS_OID; + classObjectId[1] = INT4_BTREE_OPS_OID; - index_endscan(idxtgscan); - index_close(irel); + toast_idxid = index_create(toast_relid, toast_idxname, indexInfo, + BTREE_AM_OID, classObjectId, + true, true); - heap_close(tgrel, RowExclusiveLock); + /* + * 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); /* - * Increment cmd counter to make updates visible; this is needed in - * case the same tuple has to be updated again by next pass (can - * happen in case of a self-referential FK relationship). + * 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 c00b4bebac8f9bdc5f1d6dbc5ead48107df4c307..b826297a7189aff893d02c2b15569e2c8ac3d9b1 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 eb80acbd008396e13b71ef3d97d2112a04505e47..d9d219edd0087f612f170d0cbef25f0af27a5d18 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 f3dfcd6b9b1af0c6c5afbb14725e36b6ceb618e1..1ce35eb164806fb9f17481129440510c97a68bbd 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 4166b47bb74dfd649fc3f760d1d94bbf71e8ec4d..4719bf0c914c5006c34aaad1fb167913e8e9e09a 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);