diff options
-rw-r--r-- | src/backend/commands/tablecmds.c | 2958 | ||||
-rw-r--r-- | src/backend/commands/trigger.c | 119 | ||||
-rw-r--r-- | src/backend/commands/user.c | 4 | ||||
-rw-r--r-- | src/include/commands/tablecmds.h | 6 | ||||
-rw-r--r-- | src/include/commands/trigger.h | 4 |
5 files changed, 1543 insertions, 1548 deletions
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index e8b0d8bc013..7bdc238e77c 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -1,14 +1,14 @@ /*------------------------------------------------------------------------- * * tablecmds.c - * Commands for altering table structures and settings + * Commands for creating and altering table structures and settings * * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/tablecmds.c,v 1.9 2002/04/24 02:48:54 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/tablecmds.c,v 1.10 2002/04/26 19:29:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -48,17 +48,16 @@ #include "utils/relcache.h" -static void drop_default(Oid relid, int16 attnum); -static bool needs_toast_table(Relation rel); -static void CheckTupleType(Form_pg_class tuple_class); - +static List *MergeDomainAttributes(List *schema); static List *MergeAttributes(List *schema, List *supers, bool istemp, List **supOids, List **supconstr, bool *supHasOids); static bool change_varattnos_of_a_node(Node *node, const AttrNumber *newattno); static void StoreCatalogInheritance(Oid relationId, List *supers); static int findAttrByName(const char *attributeName, List *schema); static void setRelhassubclassInRelation(Oid relationId, bool relhassubclass); -static List *MergeDomainAttributes(List *schema); +static void drop_default(Oid relid, int16 attnum); +static void CheckTupleType(Form_pg_class tuple_class); +static bool needs_toast_table(Relation rel); /* Used by attribute and relation renaming routines: */ @@ -74,6 +73,1419 @@ static void update_ri_trigger_args(Oid relid, bool update_relname); +/* ---------------------------------------------------------------- + * 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; + + /* + * Truncate relname to appropriate length (probably a waste of time, + * as parser should have done this already). + */ + StrNCpy(relname, stmt->relation->relname, NAMEDATALEN); + + /* + * Look up the namespace in which we are supposed to create the + * relation. + */ + namespaceId = RangeVarGetCreationNamespace(stmt->relation); + + /* + * 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); + + /* + * Look up inheritance ancestors and generate relation schema, + * including inherited attributes. + */ + 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); + + if (old_constraints != NIL) + { + ConstrCheck *check = (ConstrCheck *) palloc(length(old_constraints) * + sizeof(ConstrCheck)); + int ncheck = 0; + + foreach(listptr, old_constraints) + { + Constraint *cdef = (Constraint *) lfirst(listptr); + + if (cdef->contype != CONSTR_CHECK) + continue; + + 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); + + /* + * 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. + * + * First, scan schema to find new column defaults. + */ + rawDefaults = NIL; + attnum = 0; + + foreach(listptr, schema) + { + ColumnDef *colDef = lfirst(listptr); + RawColumnDefault *rawEnt; + + attnum++; + + if (colDef->raw_default == NULL) + continue; + Assert(colDef->cooked_default == NULL); + + rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault)); + rawEnt->attnum = attnum; + rawEnt->raw_default = colDef->raw_default; + rawDefaults = lappend(rawDefaults, rawEnt); + } + + /* + * Parse and add the defaults/constraints, if any. + */ + if (rawDefaults || stmt->constraints) + AddRelationRawConstraints(rel, rawDefaults, stmt->constraints); + + /* + * 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. + */ +void +TruncateRelation(const RangeVar *relation) +{ + Relation rel; + Oid relid; + + /* Grab exclusive lock in preparation for truncate */ + rel = heap_openrv(relation, AccessExclusiveLock); + relid = RelationGetRelid(rel); + + if (rel->rd_rel->relkind == RELKIND_SEQUENCE) + elog(ERROR, "TRUNCATE cannot be used on sequences. '%s' is a sequence", + RelationGetRelationName(rel)); + + if (rel->rd_rel->relkind == RELKIND_VIEW) + elog(ERROR, "TRUNCATE cannot be used on views. '%s' is a view", + RelationGetRelationName(rel)); + + if (!allowSystemTableMods && IsSystemRelation(rel)) + elog(ERROR, "TRUNCATE cannot be used on system tables. '%s' is a system table", + RelationGetRelationName(rel)); + + if (!pg_class_ownercheck(relid, GetUserId())) + elog(ERROR, "you do not own relation \"%s\"", + 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. + */ + foreach(entry, schema) + { + ColumnDef *coldef = lfirst(entry); + HeapTuple tuple; + Form_pg_type typeTup; + + tuple = typenameType(coldef->typename); + typeTup = (Form_pg_type) GETSTRUCT(tuple); + + if (typeTup->typtype == 'd') + { + /* 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; + + ReleaseSysCache(tuple); + } + + 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; + + /* + * 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. + */ + foreach(entry, schema) + { + ColumnDef *coldef = lfirst(entry); + List *rest; + + foreach(rest, lnext(entry)) + { + ColumnDef *restdef = lfirst(rest); + + if (strcmp(coldef->colname, restdef->colname) == 0) + elog(ERROR, "CREATE TABLE: attribute \"%s\" duplicated", + coldef->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. + */ + child_attno = 0; + foreach(entry, supers) + { + RangeVar *parent = (RangeVar *) lfirst(entry); + Relation relation; + TupleDesc tupleDesc; + TupleConstr *constr; + AttrNumber *newattno; + AttrNumber parent_attno; + + relation = heap_openrv(parent, AccessShareLock); + + 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); + + /* + * 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); + + /* + * Reject duplications in the list of parents. + */ + if (intMember(RelationGetRelid(relation), parentOids)) + elog(ERROR, "CREATE TABLE: inherited relation \"%s\" duplicated", + parent->relname); + + parentOids = lappendi(parentOids, RelationGetRelid(relation)); + setRelhassubclassInRelation(RelationGetRelid(relation), true); + + parentHasOids |= relation->rd_rel->relhasoids; + + tupleDesc = RelationGetDescr(relation); + constr = tupleDesc->constr; + + /* + * 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)); + + 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; + + /* + * 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; + } + + /* + * Copy default if any + */ + if (attribute->atthasdef) + { + char *this_default = NULL; + AttrDefault *attrdef; + int i; + + /* 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); + + /* + * 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; + } + } + } + + /* + * 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; + + 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); + } + } + + pfree(newattno); + + /* + * 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 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 (inhSchema != NIL) + { + foreach(entry, schema) + { + ColumnDef *newdef = lfirst(entry); + char *attributeName = newdef->colname; + int exist_attno; + + /* + * Does it conflict with some previously inherited column? + */ + exist_attno = findAttrByName(attributeName, inhSchema); + if (exist_attno > 0) + { + ColumnDef *def; + + /* + * 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; + } + + /* + * If we found any conflicting parent default values, check to make + * sure they were overridden by the child. + */ + if (have_bogus_defaults) + { + foreach(entry, schema) + { + ColumnDef *def = lfirst(entry); + + 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); + } + } + + *supOids = parentOids; + *supconstr = constraints; + *supHasOids = parentHasOids; + return schema; +} + +/* + * 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)) + { + Var *var = (Var *) node; + + if (var->varlevelsup == 0 && var->varno == 1 && + var->varattno > 0) + { + /* + * ??? 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]; + } + return false; + } + 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); +} + +/* + * 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) +{ + Relation relation; + TupleDesc desc; + int16 seqNumber; + List *entry; + HeapTuple tuple; + + /* + * sanity checks + */ + AssertArg(OidIsValid(relationId)); + + if (supers == NIL) + return; + + /* + * Catalog INHERITS information using direct ancestors only. + */ + relation = heap_openr(InheritsRelationName, RowExclusiveLock); + desc = RelationGetDescr(relation); + + seqNumber = 1; + foreach(entry, supers) + { + Oid entryOid = lfirsti(entry); + Datum datum[Natts_pg_inherits]; + char nullarr[Natts_pg_inherits]; + + datum[0] = ObjectIdGetDatum(relationId); /* inhrel */ + datum[1] = ObjectIdGetDatum(entryOid); /* inhparent */ + datum[2] = Int16GetDatum(seqNumber); /* inhseqno */ + + nullarr[0] = ' '; + nullarr[1] = ' '; + nullarr[2] = ' '; + + tuple = heap_formtuple(desc, datum, nullarr); + + heap_insert(relation, tuple); + + if (RelationGetForm(relation)->relhasindex) + { + Relation idescs[Num_pg_inherits_indices]; + + CatalogOpenIndices(Num_pg_inherits_indices, Name_pg_inherits_indices, idescs); + CatalogIndexInsert(idescs, Num_pg_inherits_indices, relation, tuple); + CatalogCloseIndices(Num_pg_inherits_indices, idescs); + } + + heap_freetuple(tuple); + + seqNumber += 1; + } + + heap_close(relation, RowExclusiveLock); + + /* ---------------- + * 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) + { + HeapTuple tuple; + Oid id; + int16 number; + List *next; + List *current; + + id = (Oid) lfirsti(entry); + current = entry; + next = lnext(entry); + + for (number = 1;; number += 1) + { + 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); + + current = lnext(current); + } + lnext(current) = next; + } + + /* + * 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; + } + } +} + +/* + * 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; + + 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); +} + + +/* + * 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 +renameatt(Oid relid, + const char *oldattname, + const char *newattname, + bool recurse) +{ + 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. + */ + targetrelation = heap_open(relid, 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 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(relid); + + /* + * 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 == relid) + continue; + /* note we need not recurse again! */ + renameatt(childrelid, oldattname, newattname, false); + } + } + + attrelation = heap_openr(AttributeRelationName, RowExclusiveLock); + + atttup = SearchSysCacheCopy(ATTNAME, + ObjectIdGetDatum(relid), + PointerGetDatum(oldattname), + 0, 0); + if (!HeapTupleIsValid(atttup)) + elog(ERROR, "renameatt: attribute \"%s\" does not exist", oldattname); + + if (((Form_pg_attribute) GETSTRUCT(atttup))->attnum < 0) + elog(ERROR, "renameatt: system attribute \"%s\" not renamed", oldattname); + + /* should not already exist */ + if (SearchSysCacheExists(ATTNAME, + ObjectIdGetDatum(relid), + PointerGetDatum(newattname), + 0, 0)) + elog(ERROR, "renameatt: attribute \"%s\" exists", newattname); + + namestrcpy(&(((Form_pg_attribute) GETSTRUCT(atttup))->attname), + newattname); + + simple_heap_update(attrelation, &atttup->t_self, atttup); + + /* keep system catalog indices current */ + { + Relation irelations[Num_pg_attr_indices]; + + CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, irelations); + CatalogIndexInsert(irelations, Num_pg_attr_indices, attrelation, atttup); + CatalogCloseIndices(Num_pg_attr_indices, irelations); + } + + heap_freetuple(atttup); + + /* + * Update column names of indexes that refer to the column being + * renamed. + */ + indexoidlist = RelationGetIndexList(targetrelation); + + foreach(indexoidscan, indexoidlist) + { + Oid indexoid = lfirsti(indexoidscan); + HeapTuple indextup; + + /* + * 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); + + /* + * 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 */ + + /* + * Update the (copied) attribute tuple. + */ + namestrcpy(&(((Form_pg_attribute) GETSTRUCT(atttup))->attname), + newattname); + + simple_heap_update(attrelation, &atttup->t_self, atttup); + + /* keep system catalog indices current */ + { + Relation irelations[Num_pg_attr_indices]; + + CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, irelations); + CatalogIndexInsert(irelations, Num_pg_attr_indices, attrelation, atttup); + CatalogCloseIndices(Num_pg_attr_indices, irelations); + } + 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! */ +} + +/* + * 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 +renamerel(Oid relid, const char *newrelname) +{ + Relation targetrelation; + Relation relrelation; /* for RELATION relation */ + HeapTuple reltup; + Oid namespaceId; + char *oldrelname; + char relkind; + bool relhastriggers; + Relation irelations[Num_pg_class_indices]; + + /* + * Grab an exclusive lock on the target table or index, which we will + * NOT release until end of transaction. + */ + 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); + + /* + * Find relation's pg_class tuple, and make sure newrelname isn't in + * use. + */ + relrelation = heap_openr(RelationRelationName, RowExclusiveLock); + + reltup = SearchSysCacheCopy(RELOID, + PointerGetDatum(relid), + 0, 0, 0); + if (!HeapTupleIsValid(reltup)) + elog(ERROR, "renamerel: relation \"%s\" does not exist", + oldrelname); + + if (get_relname_relid(newrelname, namespaceId) != InvalidOid) + elog(ERROR, "renamerel: relation \"%s\" exists", newrelname); + + /* + * 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); + + 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); + + /* + * Also rename the associated type, if any. + */ + 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); + } + + /* + * Close rel, but keep exclusive lock! + */ + relation_close(targetrelation, 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) +{ + 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; +} + +/* + * 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; + } + + /* + * 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); + + 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; + } + } + } + + if (!changed) + { + /* Don't need to update this tuple */ + ReleaseBuffer(buffer); + continue; + } + + /* + * 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; + } + + /* + * 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'; + + tuple = heap_modifytuple(tuple, tgrel, values, nulls, replaces); + + /* + * Now we can release hold on original tuple. + */ + ReleaseBuffer(buffer); + + /* + * Update pg_trigger and its indexes + */ + simple_heap_update(tgrel, &tuple->t_self, tuple); + + { + 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); + } + + /* free up our scratch memory */ + pfree(newtgargs); + heap_freetuple(tuple); + } + + index_endscan(idxtgscan); + index_close(irel); + + heap_close(tgrel, RowExclusiveLock); + + /* + * 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(); +} + + /* ---------------- * AlterTableAddColumn * (formerly known as PerformAddAttribute) @@ -920,7 +2332,6 @@ AlterTableAlterColumnFlags(Oid myrelid, } - /* * ALTER TABLE DROP COLUMN */ @@ -933,7 +2344,6 @@ AlterTableDropColumn(Oid myrelid, } - /* * ALTER TABLE ADD CONSTRAINT */ @@ -1247,7 +2657,6 @@ AlterTableAddConstraint(Oid myrelid, } - /* * ALTER TABLE DROP CONSTRAINT * Note: It is legal to remove a constraint with name "" as it is possible @@ -1664,1532 +3073,3 @@ needs_toast_table(Relation rel) 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; - - /* - * Truncate relname to appropriate length (probably a waste of time, - * as parser should have done this already). - */ - StrNCpy(relname, stmt->relation->relname, NAMEDATALEN); - - /* - * Look up the namespace in which we are supposed to create the - * relation. - */ - namespaceId = RangeVarGetCreationNamespace(stmt->relation); - - /* - * 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); - - /* - * Look up inheritance ancestors and generate relation schema, - * including inherited attributes. - */ - 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); - - if (old_constraints != NIL) - { - ConstrCheck *check = (ConstrCheck *) palloc(length(old_constraints) * - sizeof(ConstrCheck)); - int ncheck = 0; - - foreach(listptr, old_constraints) - { - Constraint *cdef = (Constraint *) lfirst(listptr); - - if (cdef->contype != CONSTR_CHECK) - continue; - - 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); - - /* - * 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. - * - * First, scan schema to find new column defaults. - */ - rawDefaults = NIL; - attnum = 0; - - foreach(listptr, schema) - { - ColumnDef *colDef = lfirst(listptr); - RawColumnDefault *rawEnt; - - attnum++; - - if (colDef->raw_default == NULL) - continue; - Assert(colDef->cooked_default == NULL); - - rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault)); - rawEnt->attnum = attnum; - rawEnt->raw_default = colDef->raw_default; - rawDefaults = lappend(rawDefaults, rawEnt); - } - - /* - * Parse and add the defaults/constraints, if any. - */ - if (rawDefaults || stmt->constraints) - AddRelationRawConstraints(rel, rawDefaults, stmt->constraints); - - /* - * 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. - */ -void -TruncateRelation(const RangeVar *relation) -{ - Relation rel; - Oid relid; - - /* Grab exclusive lock in preparation for truncate */ - rel = heap_openrv(relation, AccessExclusiveLock); - relid = RelationGetRelid(rel); - - if (rel->rd_rel->relkind == RELKIND_SEQUENCE) - elog(ERROR, "TRUNCATE cannot be used on sequences. '%s' is a sequence", - RelationGetRelationName(rel)); - - if (rel->rd_rel->relkind == RELKIND_VIEW) - elog(ERROR, "TRUNCATE cannot be used on views. '%s' is a view", - RelationGetRelationName(rel)); - - if (!allowSystemTableMods && IsSystemRelation(rel)) - elog(ERROR, "TRUNCATE cannot be used on system tables. '%s' is a system table", - RelationGetRelationName(rel)); - - if (!pg_class_ownercheck(relid, GetUserId())) - elog(ERROR, "you do not own relation \"%s\"", - 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. - */ - foreach(entry, schema) - { - ColumnDef *coldef = lfirst(entry); - HeapTuple tuple; - Form_pg_type typeTup; - - tuple = typenameType(coldef->typename); - typeTup = (Form_pg_type) GETSTRUCT(tuple); - - if (typeTup->typtype == 'd') - { - /* 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; - - ReleaseSysCache(tuple); - } - - 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; - - /* - * 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. - */ - foreach(entry, schema) - { - ColumnDef *coldef = lfirst(entry); - List *rest; - - foreach(rest, lnext(entry)) - { - ColumnDef *restdef = lfirst(rest); - - if (strcmp(coldef->colname, restdef->colname) == 0) - elog(ERROR, "CREATE TABLE: attribute \"%s\" duplicated", - coldef->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. - */ - child_attno = 0; - foreach(entry, supers) - { - RangeVar *parent = (RangeVar *) lfirst(entry); - Relation relation; - TupleDesc tupleDesc; - TupleConstr *constr; - AttrNumber *newattno; - AttrNumber parent_attno; - - relation = heap_openrv(parent, AccessShareLock); - - 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); - - /* - * 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); - - /* - * Reject duplications in the list of parents. - */ - if (intMember(RelationGetRelid(relation), parentOids)) - elog(ERROR, "CREATE TABLE: inherited relation \"%s\" duplicated", - parent->relname); - - parentOids = lappendi(parentOids, RelationGetRelid(relation)); - setRelhassubclassInRelation(RelationGetRelid(relation), true); - - parentHasOids |= relation->rd_rel->relhasoids; - - tupleDesc = RelationGetDescr(relation); - constr = tupleDesc->constr; - - /* - * 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)); - - 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; - - /* - * 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; - } - - /* - * Copy default if any - */ - if (attribute->atthasdef) - { - char *this_default = NULL; - AttrDefault *attrdef; - int i; - - /* 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); - - /* - * 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; - } - } - } - - /* - * 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; - - 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); - } - } - - pfree(newattno); - - /* - * 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 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 (inhSchema != NIL) - { - foreach(entry, schema) - { - ColumnDef *newdef = lfirst(entry); - char *attributeName = newdef->colname; - int exist_attno; - - /* - * Does it conflict with some previously inherited column? - */ - exist_attno = findAttrByName(attributeName, inhSchema); - if (exist_attno > 0) - { - ColumnDef *def; - - /* - * 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; - } - - /* - * If we found any conflicting parent default values, check to make - * sure they were overridden by the child. - */ - if (have_bogus_defaults) - { - foreach(entry, schema) - { - ColumnDef *def = lfirst(entry); - - 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); - } - } - - *supOids = parentOids; - *supconstr = constraints; - *supHasOids = parentHasOids; - return schema; -} - -/* - * 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)) - { - Var *var = (Var *) node; - - if (var->varlevelsup == 0 && var->varno == 1 && - var->varattno > 0) - { - /* - * ??? 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]; - } - return false; - } - 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); -} - -/* - * 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) -{ - Relation relation; - TupleDesc desc; - int16 seqNumber; - List *entry; - HeapTuple tuple; - - /* - * sanity checks - */ - AssertArg(OidIsValid(relationId)); - - if (supers == NIL) - return; - - /* - * Catalog INHERITS information using direct ancestors only. - */ - relation = heap_openr(InheritsRelationName, RowExclusiveLock); - desc = RelationGetDescr(relation); - - seqNumber = 1; - foreach(entry, supers) - { - Oid entryOid = lfirsti(entry); - Datum datum[Natts_pg_inherits]; - char nullarr[Natts_pg_inherits]; - - datum[0] = ObjectIdGetDatum(relationId); /* inhrel */ - datum[1] = ObjectIdGetDatum(entryOid); /* inhparent */ - datum[2] = Int16GetDatum(seqNumber); /* inhseqno */ - - nullarr[0] = ' '; - nullarr[1] = ' '; - nullarr[2] = ' '; - - tuple = heap_formtuple(desc, datum, nullarr); - - heap_insert(relation, tuple); - - if (RelationGetForm(relation)->relhasindex) - { - Relation idescs[Num_pg_inherits_indices]; - - CatalogOpenIndices(Num_pg_inherits_indices, Name_pg_inherits_indices, idescs); - CatalogIndexInsert(idescs, Num_pg_inherits_indices, relation, tuple); - CatalogCloseIndices(Num_pg_inherits_indices, idescs); - } - - heap_freetuple(tuple); - - seqNumber += 1; - } - - heap_close(relation, RowExclusiveLock); - - /* ---------------- - * 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) - { - HeapTuple tuple; - Oid id; - int16 number; - List *next; - List *current; - - id = (Oid) lfirsti(entry); - current = entry; - next = lnext(entry); - - for (number = 1;; number += 1) - { - 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); - - current = lnext(current); - } - lnext(current) = next; - } - - /* - * 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; - } - } -} - -/* - * 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; - - 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); -} - - -/* - * 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 -renameatt(Oid relid, - const char *oldattname, - const char *newattname, - bool recurse) -{ - 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. - */ - targetrelation = heap_open(relid, 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 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(relid); - - /* - * 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 == relid) - continue; - /* note we need not recurse again! */ - renameatt(childrelid, oldattname, newattname, false); - } - } - - attrelation = heap_openr(AttributeRelationName, RowExclusiveLock); - - atttup = SearchSysCacheCopy(ATTNAME, - ObjectIdGetDatum(relid), - PointerGetDatum(oldattname), - 0, 0); - if (!HeapTupleIsValid(atttup)) - elog(ERROR, "renameatt: attribute \"%s\" does not exist", oldattname); - - if (((Form_pg_attribute) GETSTRUCT(atttup))->attnum < 0) - elog(ERROR, "renameatt: system attribute \"%s\" not renamed", oldattname); - - /* should not already exist */ - if (SearchSysCacheExists(ATTNAME, - ObjectIdGetDatum(relid), - PointerGetDatum(newattname), - 0, 0)) - elog(ERROR, "renameatt: attribute \"%s\" exists", newattname); - - namestrcpy(&(((Form_pg_attribute) GETSTRUCT(atttup))->attname), - newattname); - - simple_heap_update(attrelation, &atttup->t_self, atttup); - - /* keep system catalog indices current */ - { - Relation irelations[Num_pg_attr_indices]; - - CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, irelations); - CatalogIndexInsert(irelations, Num_pg_attr_indices, attrelation, atttup); - CatalogCloseIndices(Num_pg_attr_indices, irelations); - } - - heap_freetuple(atttup); - - /* - * Update column names of indexes that refer to the column being - * renamed. - */ - indexoidlist = RelationGetIndexList(targetrelation); - - foreach(indexoidscan, indexoidlist) - { - Oid indexoid = lfirsti(indexoidscan); - HeapTuple indextup; - - /* - * 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); - - /* - * 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 */ - - /* - * Update the (copied) attribute tuple. - */ - namestrcpy(&(((Form_pg_attribute) GETSTRUCT(atttup))->attname), - newattname); - - simple_heap_update(attrelation, &atttup->t_self, atttup); - - /* keep system catalog indices current */ - { - Relation irelations[Num_pg_attr_indices]; - - CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, irelations); - CatalogIndexInsert(irelations, Num_pg_attr_indices, attrelation, atttup); - CatalogCloseIndices(Num_pg_attr_indices, irelations); - } - 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! */ -} - -/* - * 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 -renamerel(Oid relid, const char *newrelname) -{ - Relation targetrelation; - Relation relrelation; /* for RELATION relation */ - HeapTuple reltup; - Oid namespaceId; - char *oldrelname; - char relkind; - bool relhastriggers; - Relation irelations[Num_pg_class_indices]; - - /* - * Grab an exclusive lock on the target table or index, which we will - * NOT release until end of transaction. - */ - 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); - - /* - * Find relation's pg_class tuple, and make sure newrelname isn't in - * use. - */ - relrelation = heap_openr(RelationRelationName, RowExclusiveLock); - - reltup = SearchSysCacheCopy(RELOID, - PointerGetDatum(relid), - 0, 0, 0); - if (!HeapTupleIsValid(reltup)) - elog(ERROR, "renamerel: relation \"%s\" does not exist", - oldrelname); - - if (get_relname_relid(newrelname, namespaceId) != InvalidOid) - elog(ERROR, "renamerel: relation \"%s\" exists", newrelname); - - /* - * 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); - - 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); - - /* - * Also rename the associated type, if any. - */ - 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); - } - - /* - * Close rel, but keep exclusive lock! - */ - relation_close(targetrelation, NoLock); -} - -/* - * renametrig - changes the name of a trigger on a relation - * - * trigger name is changed in trigger catalog. - * No record of the previous name is kept. - * - * get proper relrelation from relation catalog (if not arg) - * scan trigger catalog - * for name conflict (within rel) - * for original trigger (if not arg) - * modify tgname in trigger tuple - * insert modified trigger in trigger catalog - * delete original trigger from trigger catalog - */ -extern void renametrig(Oid relid, - const char *oldname, - const char *newname) -{ - Relation targetrel; - Relation tgrel; - HeapTuple tuple; - SysScanDesc tgscan; - ScanKeyData key; - bool found = FALSE; - Relation idescs[Num_pg_trigger_indices]; - - /* - * Grab an exclusive lock on the target table, which we will NOT - * release until end of transaction. - */ - targetrel = heap_open(relid, AccessExclusiveLock); - - /* - * Scan pg_trigger twice for existing triggers on relation. We do this in - * order to ensure a trigger does not exist with newname (The unique index - * on tgrelid/tgname would complain anyway) and to ensure a trigger does - * exist with oldname. - * - * NOTE that this is cool only because we have AccessExclusiveLock on the - * relation, so the trigger set won't be changing underneath us. - */ - tgrel = heap_openr(TriggerRelationName, RowExclusiveLock); - - /* - * First pass -- look for name conflict - */ - ScanKeyEntryInitialize(&key, 0, - Anum_pg_trigger_tgrelid, - F_OIDEQ, - ObjectIdGetDatum(relid)); - tgscan = systable_beginscan(tgrel, TriggerRelidNameIndex, true, - SnapshotNow, 1, &key); - while (HeapTupleIsValid(tuple = systable_getnext(tgscan))) - { - Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple); - - if (namestrcmp(&(pg_trigger->tgname), newname) == 0) - elog(ERROR, "renametrig: trigger %s already defined on relation %s", - newname, RelationGetRelationName(targetrel)); - } - systable_endscan(tgscan); - - /* - * Second pass -- look for trigger existing with oldname and update - */ - ScanKeyEntryInitialize(&key, 0, - Anum_pg_trigger_tgrelid, - F_OIDEQ, - ObjectIdGetDatum(relid)); - tgscan = systable_beginscan(tgrel, TriggerRelidNameIndex, true, - SnapshotNow, 1, &key); - while (HeapTupleIsValid(tuple = systable_getnext(tgscan))) - { - Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple); - - if (namestrcmp(&(pg_trigger->tgname), oldname) == 0) - { - /* - * Update pg_trigger tuple with new tgname. - * (Scribbling on tuple is OK because it's a copy...) - */ - namestrcpy(&(pg_trigger->tgname), newname); - simple_heap_update(tgrel, &tuple->t_self, tuple); - - /* - * keep system catalog indices current - */ - CatalogOpenIndices(Num_pg_trigger_indices, Name_pg_trigger_indices, idescs); - CatalogIndexInsert(idescs, Num_pg_trigger_indices, tgrel, tuple); - CatalogCloseIndices(Num_pg_trigger_indices, idescs); - - /* - * Invalidate relation's relcache entry so that other - * backends (and this one too!) are sent SI message to make them - * rebuild relcache entries. - */ - CacheInvalidateRelcache(relid); - - found = TRUE; - break; - } - } - systable_endscan(tgscan); - - heap_close(tgrel, RowExclusiveLock); - - if (!found) - elog(ERROR, "renametrig: trigger %s not defined on relation %s", - oldname, RelationGetRelationName(targetrel)); - - /* - * Close rel, but keep exclusive lock! - */ - heap_close(targetrel, NoLock); -} - - -/* - * Given a trigger function OID, determine whether it is an RI trigger, - * 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; -} - -/* - * 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; - } - - /* - * 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); - - 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; - } - } - } - - if (!changed) - { - /* Don't need to update this tuple */ - ReleaseBuffer(buffer); - continue; - } - - /* - * 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; - } - - /* - * 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'; - - tuple = heap_modifytuple(tuple, tgrel, values, nulls, replaces); - - /* - * Now we can release hold on original tuple. - */ - ReleaseBuffer(buffer); - - /* - * Update pg_trigger and its indexes - */ - simple_heap_update(tgrel, &tuple->t_self, tuple); - - { - 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); - } - - /* free up our scratch memory */ - pfree(newtgargs); - heap_freetuple(tuple); - } - - index_endscan(idxtgscan); - index_close(irel); - - heap_close(tgrel, RowExclusiveLock); - - /* - * 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(); -} diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index c00b4bebac8..b826297a718 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.114 2002/04/19 16:36:08 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.115 2002/04/26 19:29:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -504,6 +504,123 @@ RelationRemoveTriggers(Relation rel) } /* + * 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. * * Note that trigger data must be allocated in CacheMemoryContext diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c index eb80acbd008..d9d219edd00 100644 --- a/src/backend/commands/user.c +++ b/src/backend/commands/user.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Header: /cvsroot/pgsql/src/backend/commands/user.c,v 1.96 2002/04/18 21:16:16 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/user.c,v 1.97 2002/04/26 19:29:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -646,7 +646,7 @@ CreateUser(CreateUserStmt *stmt) /* * ALTER USER */ -extern void +void AlterUser(AlterUserStmt *stmt) { Datum new_record[Natts_pg_shadow]; diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h index f3dfcd6b9b1..1ce35eb1648 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: tablecmds.h,v 1.2 2002/04/24 02:48:55 momjian Exp $ + * $Id: tablecmds.h,v 1.3 2002/04/26 19:29:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -61,8 +61,4 @@ extern void renameatt(Oid relid, extern void renamerel(Oid relid, const char *newrelname); -extern void renametrig(Oid relid, - const char *oldname, - const char *newname); - #endif /* TABLECMDS_H */ diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 4166b47bb74..4719bf0c914 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: trigger.h,v 1.34 2002/04/01 22:36:13 tgl Exp $ + * $Id: trigger.h,v 1.35 2002/04/26 19:29:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -105,6 +105,8 @@ extern void CreateTrigger(CreateTrigStmt *stmt); extern void DropTrigger(Oid relid, const char *trigname); extern void RelationRemoveTriggers(Relation rel); +extern void renametrig(Oid relid, const char *oldname, const char *newname); + extern void RelationBuildTriggers(Relation relation); extern void FreeTriggerDesc(TriggerDesc *trigdesc); |