/*-------------------------------------------------------------------------
*
* 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 $
*
*-------------------------------------------------------------------------
*/
#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: */
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
{
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 = "";
-
- /*
- * 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 = "";
- 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
{
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 = "";
+
+ /*
+ * We need to make a parse state and range
+ * table to allow us to transformExpr and
+ * fix_opids to get a version of the
+ * expression we can pass to ExecQual
+ */
+ pstate = make_parsestate(NULL);
+ rte = addRangeTableEntryForRelation(pstate,
+ myrelid,
+ makeAlias(RelationGetRelationName(rel), NIL),
+ false,
+ true);
+ addRTEtoQuery(pstate, rte, true, true);
+
+ /*
+ * Convert the A_EXPR in raw_expr into an
+ * EXPR
+ */
+ expr = transformExpr(pstate, constr->raw_expr);
+
+ /*
+ * Make sure it yields a boolean result.
+ */
+ if (exprType(expr) != BOOLOID)
+ elog(ERROR, "CHECK '%s' does not yield boolean result",
+ name);
+
+ /*
+ * Make sure no outside relations are
+ * referred to.
+ */
+ if (length(pstate->p_rtable) != 1)
+ elog(ERROR, "Only relation '%s' can be referenced in CHECK",
+ RelationGetRelationName(rel));
+
+ /*
+ * Might as well try to reduce any
+ * constant expressions.
+ */
+ expr = eval_const_expressions(expr);
+
+ /* And fix the opids */
+ fix_opids(expr);
+
+ qual = makeList1(expr);
+
+ /* Make tuple slot to hold tuples */
+ slot = MakeTupleTableSlot();
+ ExecSetSlotDescriptor(slot, RelationGetDescr(rel), false);
+ /* Make an expression context for ExecQual */
+ econtext = MakeExprContext(slot, CurrentMemoryContext);
+
+ /*
+ * Scan through the rows now, checking the
+ * expression at each row.
+ */
+ scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL);
+
+ while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
+ {
+ ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+ if (!ExecQual(qual, econtext, true))
+ {
+ successful = false;
+ break;
+ }
+ ResetExprContext(econtext);
+ }
+
+ heap_endscan(scan);
+
+ FreeExprContext(econtext);
+ pfree(slot);
+
+ if (!successful)
+ elog(ERROR, "AlterTableAddConstraint: rejected due to CHECK constraint %s", name);
+
+ /*
+ * Call AddRelationRawConstraints to do
+ * the real adding -- It duplicates some
+ * of the above, but does not check the
+ * validity of the constraint against
+ * tuples already in the table.
+ */
+ AddRelationRawConstraints(rel, NIL,
+ makeList1(constr));
+
+ break;
+ }
+ default:
+ elog(ERROR, "ALTER TABLE / ADD CONSTRAINT is not implemented for that constraint type.");
+ }
+ break;
+ }
+ case T_FkConstraint:
+ {
+ FkConstraint *fkconstraint = (FkConstraint *) newConstraint;
+ Relation pkrel;
+ HeapScanDesc scan;
+ HeapTuple tuple;
+ Trigger trig;
+ List *list;
+ int count;
+
+ /*
+ * Grab an exclusive lock on the pk table, so that
+ * someone doesn't delete rows out from under us.
+ *
+ * XXX wouldn't a lesser lock be sufficient?
+ */
+ pkrel = heap_openrv(fkconstraint->pktable,
+ AccessExclusiveLock);
+
+ /*
+ * Validity checks
+ */
+ if (pkrel->rd_rel->relkind != RELKIND_RELATION)
+ elog(ERROR, "referenced table \"%s\" not a relation",
+ fkconstraint->pktable->relname);
+
+ if (isTempNamespace(RelationGetNamespace(pkrel)) &&
+ !isTempNamespace(RelationGetNamespace(rel)))
+ elog(ERROR, "ALTER TABLE / ADD CONSTRAINT: Unable to reference temporary table from permanent table constraint.");
+
+ /*
+ * First we check for limited correctness of the
+ * constraint.
+ *
+ * NOTE: we assume parser has already checked for
+ * existence of an appropriate unique index on the
+ * referenced relation, and that the column datatypes
+ * are comparable.
+ *
+ * Scan through each tuple, calling RI_FKey_check_ins
+ * (insert trigger) as if that tuple had just been
+ * inserted. If any of those fail, it should
+ * elog(ERROR) and that's that.
+ */
+ MemSet(&trig, 0, sizeof(trig));
+ trig.tgoid = InvalidOid;
+ if (fkconstraint->constr_name)
+ trig.tgname = fkconstraint->constr_name;
+ else
+ trig.tgname = "";
+ trig.tgenabled = TRUE;
+ trig.tgisconstraint = TRUE;
+ trig.tgconstrrelid = RelationGetRelid(pkrel);
+ trig.tgdeferrable = FALSE;
+ trig.tginitdeferred = FALSE;
+
+ trig.tgargs = (char **) palloc(
+ sizeof(char *) * (4 + length(fkconstraint->fk_attrs)
+ + length(fkconstraint->pk_attrs)));
+
+ trig.tgargs[0] = trig.tgname;
+ trig.tgargs[1] = RelationGetRelationName(rel);
+ trig.tgargs[2] = RelationGetRelationName(pkrel);
+ trig.tgargs[3] = fkconstraint->match_type;
+ count = 4;
+ foreach(list, fkconstraint->fk_attrs)
+ {
+ Ident *fk_at = lfirst(list);
- 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);
+}