From 077db40fa1f3ef93a60d995cc5b2666474f81302 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 5 May 2004 04:48:48 +0000 Subject: [PATCH] ALTER TABLE rewrite. New cool stuff: * ALTER ... ADD COLUMN with defaults and NOT NULL constraints works per SQL spec. A default is implemented by rewriting the table with the new value stored in each row. * ALTER COLUMN TYPE. You can change a column's datatype to anything you want, so long as you can specify how to convert the old value. Rewrites the table. (Possible future improvement: optimize no-op conversions such as varchar(N) to varchar(N+1).) * Multiple ALTER actions in a single ALTER TABLE command. You can perform any number of column additions, type changes, and constraint additions with only one pass over the table contents. Basic documentation provided in ALTER TABLE ref page, but some more docs work is needed. Original patch from Rod Taylor, additional work from Tom Lane. --- doc/src/sgml/ref/alter_table.sgml | 189 +- src/backend/bootstrap/bootparse.y | 10 +- src/backend/catalog/dependency.c | 95 +- src/backend/catalog/heap.c | 29 +- src/backend/catalog/index.c | 27 +- src/backend/commands/cluster.c | 81 +- src/backend/commands/indexcmds.c | 246 +- src/backend/commands/tablecmds.c | 3153 ++++++++++++++------- src/backend/nodes/copyfuncs.c | 18 +- src/backend/nodes/equalfuncs.c | 16 +- src/backend/parser/analyze.c | 419 ++- src/backend/parser/gram.y | 189 +- src/backend/tcop/utility.c | 131 +- src/backend/utils/adt/ruleutils.c | 214 +- src/include/catalog/dependency.h | 31 +- src/include/catalog/heap.h | 14 +- src/include/catalog/index.h | 5 +- src/include/commands/cluster.h | 6 +- src/include/commands/defrem.h | 10 +- src/include/commands/tablecmds.h | 43 +- src/include/nodes/nodes.h | 3 +- src/include/nodes/parsenodes.h | 71 +- src/include/parser/analyze.h | 5 +- src/include/utils/builtins.h | 4 +- src/test/regress/expected/alter_table.out | 96 +- src/test/regress/expected/foreign_key.out | 22 + src/test/regress/expected/inherit.out | 9 + src/test/regress/sql/alter_table.sql | 59 +- src/test/regress/sql/foreign_key.sql | 6 + src/test/regress/sql/inherit.sql | 7 + 30 files changed, 3302 insertions(+), 1906 deletions(-) diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index c249ffbc54..6119a15062 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -1,5 +1,5 @@ @@ -21,31 +21,26 @@ PostgreSQL documentation ALTER TABLE [ ONLY ] name [ * ] - ADD [ COLUMN ] column type [ column_constraint [ ... ] ] + action [, ... ] ALTER TABLE [ ONLY ] name [ * ] - DROP [ COLUMN ] column [ RESTRICT | CASCADE ] -ALTER TABLE [ ONLY ] name [ * ] - ALTER [ COLUMN ] column { SET DEFAULT expression | DROP DEFAULT } -ALTER TABLE [ ONLY ] name [ * ] - ALTER [ COLUMN ] column { SET | DROP } NOT NULL -ALTER TABLE [ ONLY ] name [ * ] - ALTER [ COLUMN ] column SET STATISTICS integer -ALTER TABLE [ ONLY ] name [ * ] - ALTER [ COLUMN ] column SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN } -ALTER TABLE [ ONLY ] name [ * ] - SET WITHOUT OIDS -ALTER TABLE [ ONLY ] name [ * ] - RENAME [ COLUMN ] column TO new_column + RENAME [ COLUMN ] column TO new_column ALTER TABLE name RENAME TO new_name -ALTER TABLE [ ONLY ] name [ * ] + +where action is one of: + + ADD [ COLUMN ] column type [ column_constraint [ ... ] ] + DROP [ COLUMN ] column [ RESTRICT | CASCADE ] + ALTER [ COLUMN ] column TYPE type [ USING expression ] + ALTER [ COLUMN ] column SET DEFAULT expression + ALTER [ COLUMN ] column DROP DEFAULT + ALTER [ COLUMN ] column { SET | DROP } NOT NULL + ALTER [ COLUMN ] column SET STATISTICS integer + ALTER [ COLUMN ] column SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN } ADD table_constraint -ALTER TABLE [ ONLY ] name [ * ] DROP CONSTRAINT constraint_name [ RESTRICT | CASCADE ] -ALTER TABLE name + SET WITHOUT OIDS OWNER TO new_owner -ALTER TABLE name CLUSTER ON index_name @@ -81,6 +76,23 @@ ALTER TABLE name + + ALTER COLUMN TYPE + + + This form changes the type of a column of a table. Indexes and + simple table constraints involving the column will be automatically + converted to use the new column type by reparsing the originally + supplied expression. The optional USING + clause specifies how to compute the new column value from the old; + if omitted, the default conversion is the same as an assignment + cast from old data type to new. A USING + clause must be provided if there is no implicit or assignment + cast from old to new type. + + + + SET/DROP DEFAULT @@ -146,6 +158,28 @@ ALTER TABLE name + + ADD table_constraint + + + This form adds a new constraint to a table using the same syntax as + . + + + + + + DROP CONSTRAINT + + + This form drops constraints on a table. + Currently, constraints on tables are not required to have unique + names, so there may be more than one constraint matching the specified + name. All matching constraints will be dropped. + + + + SET WITHOUT OIDS @@ -165,39 +199,6 @@ ALTER TABLE name - - RENAME - - - The RENAME forms change the name of a table - (or an index, sequence, or view) or the name of an individual column in - a table. There is no effect on the stored data. - - - - - - ADD table_constraint - - - This form adds a new constraint to a table using the same syntax as - . - - - - - - DROP CONSTRAINT - - - This form drops constraints on a table. - Currently, constraints on tables are not required to have unique - names, so there may be more than one constraint matching the specified - name. All such constraints will be dropped. - - - - OWNER @@ -212,15 +213,34 @@ ALTER TABLE name CLUSTER - This form marks a table for future + This form selects the default controlling index for future operations. + + RENAME + + + The RENAME forms change the name of a table + (or an index, sequence, or view) or the name of an individual column in + a table. There is no effect on the stored data. + + + + + + All the actions except RENAME can be combined into + a list of multiple alterations to apply in parallel. For example, it + is possible to add several columns and/or alter the type of several + columns in a single command. This is particularly useful with large + tables, since only one pass over the table need be made. + + You must own the table to use ALTER TABLE; except for ALTER TABLE OWNER, which may only be executed by a superuser. @@ -262,7 +282,8 @@ ALTER TABLE name type - Data type of the new column. + Data type of the new column, or new data type for an existing + column. @@ -352,16 +373,27 @@ ALTER TABLE name - In the current implementation of ADD COLUMN, - default and NOT NULL clauses for the new column are not supported. - The new column always comes into being with all values null. - You can use the SET DEFAULT form - of ALTER TABLE to set the default afterward. - (You may also want to update the already existing rows to the - new default value, using - .) - If you want to mark the column non-null, use the SET NOT NULL - form after you've entered non-null values for the column in all rows. + When a column is added with ADD COLUMN, all existing + rows in the table are initialized with the column's default value + (NULL if no DEFAULT clause is specified). + + + + Adding a column with a non-null default or changing the type of an + existing column will require the entire table to be rewritten. This + may take a significant amount of time for a large table; and it will + temporarily require double the disk space. + + + + Adding a CHECK or NOT NULL constraint requires + scanning the table to verify that existing rows meet the constraint. + + + + The main reason for providing the option to specify multiple changes + in a single ALTER TABLE is that multiple table scans or + rewrites can thereby be combined into a single pass over the table. @@ -381,9 +413,9 @@ VACUUM FULL table; - If a table has any descendant tables, it is not permitted to add - or rename a column in the parent table without doing the same to - the descendants. That is, ALTER TABLE ONLY + If a table has any descendant tables, it is not permitted to add, + rename, or change the type of a column in the parent table without doing + the same to the descendants. That is, ALTER TABLE ONLY will be rejected. This ensures that the descendants always have columns matching the parent. @@ -427,6 +459,15 @@ ALTER TABLE distributors DROP COLUMN address RESTRICT; + + To change the types of two existing columns in one operation: + +ALTER TABLE distributors + ALTER COLUMN address TYPE varchar(80), + ALTER COLUMN name TYPE varchar(100); + + + To rename an existing column: @@ -493,15 +534,11 @@ ALTER TABLE distributors ADD PRIMARY KEY (dist_id); Compatibility - The ADD COLUMN form conforms with the SQL - standard, with the exception that it does not support defaults and - not-null constraints, as explained above. The ALTER - COLUMN form is in full conformance. - - - - The clauses to rename tables, columns, indexes, views, and sequences are + The ADD, DROP, and SET DEFAULT + forms conform with the SQL standard. The other forms are PostgreSQL extensions of the SQL standard. + Also, the ability to specify more than one manipulation in a single + ALTER TABLE command is an extension. diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y index e6bde26d33..679ef5577f 100644 --- a/src/backend/bootstrap/bootparse.y +++ b/src/backend/bootstrap/bootparse.y @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/bootstrap/bootparse.y,v 1.65 2004/03/23 19:35:16 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/bootstrap/bootparse.y,v 1.66 2004/05/05 04:48:45 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -241,7 +241,9 @@ Boot_DeclareIndexStmt: LexIDStr($3), LexIDStr($7), $9, - false, false, false, NULL, NIL); + NULL, NIL, + false, false, false, + false, false, true, false); do_end(); } ; @@ -255,7 +257,9 @@ Boot_DeclareUniqueIndexStmt: LexIDStr($4), LexIDStr($8), $10, - true, false, false, NULL, NIL); + NULL, NIL, + true, false, false, + false, false, true, false); do_end(); } ; diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index e6552b9f47..5f2b2ea28a 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -8,7 +8,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.34 2003/11/29 19:51:42 pgsql Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.35 2004/05/05 04:48:45 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -48,25 +48,6 @@ #include "utils/syscache.h" -/* This enum covers all system catalogs whose OIDs can appear in classid. */ -typedef enum ObjectClasses -{ - OCLASS_CLASS, /* pg_class */ - OCLASS_PROC, /* pg_proc */ - OCLASS_TYPE, /* pg_type */ - OCLASS_CAST, /* pg_cast */ - OCLASS_CONSTRAINT, /* pg_constraint */ - OCLASS_CONVERSION, /* pg_conversion */ - OCLASS_DEFAULT, /* pg_attrdef */ - OCLASS_LANGUAGE, /* pg_language */ - OCLASS_OPERATOR, /* pg_operator */ - OCLASS_OPCLASS, /* pg_opclass */ - OCLASS_REWRITE, /* pg_rewrite */ - OCLASS_TRIGGER, /* pg_trigger */ - OCLASS_SCHEMA, /* pg_namespace */ - MAX_OCLASS /* MUST BE LAST */ -} ObjectClasses; - /* expansible list of ObjectAddresses */ typedef struct { @@ -113,7 +94,7 @@ static bool find_expr_references_walker(Node *node, static void eliminate_duplicate_dependencies(ObjectAddresses *addrs); static int object_address_comparator(const void *a, const void *b); static void init_object_addresses(ObjectAddresses *addrs); -static void add_object_address(ObjectClasses oclass, Oid objectId, int32 subId, +static void add_object_address(ObjectClass oclass, Oid objectId, int32 subId, ObjectAddresses *addrs); static void add_exact_object_address(const ObjectAddress *object, ObjectAddresses *addrs); @@ -121,8 +102,6 @@ static bool object_address_present(const ObjectAddress *object, ObjectAddresses *addrs); static void term_object_addresses(ObjectAddresses *addrs); static void init_object_classes(void); -static ObjectClasses getObjectClass(const ObjectAddress *object); -static char *getObjectDescription(const ObjectAddress *object); static void getRelationDescription(StringInfo buffer, Oid relid); @@ -1238,7 +1217,7 @@ init_object_addresses(ObjectAddresses *addrs) * by catalog OID. */ static void -add_object_address(ObjectClasses oclass, Oid objectId, int32 subId, +add_object_address(ObjectClass oclass, Oid objectId, int32 subId, ObjectAddresses *addrs) { ObjectAddress *item; @@ -1350,7 +1329,7 @@ init_object_classes(void) * This function is needed just because some of the system catalogs do * not have hardwired-at-compile-time OIDs. */ -static ObjectClasses +ObjectClass getObjectClass(const ObjectAddress *object) { /* Easy for the bootstrapped catalogs... */ @@ -1435,7 +1414,7 @@ getObjectClass(const ObjectAddress *object) * * The result is a palloc'd string. */ -static char * +char * getObjectDescription(const ObjectAddress *object) { StringInfoData buffer; @@ -1447,18 +1426,18 @@ getObjectDescription(const ObjectAddress *object) case OCLASS_CLASS: getRelationDescription(&buffer, object->objectId); if (object->objectSubId != 0) - appendStringInfo(&buffer, " column %s", + appendStringInfo(&buffer, gettext(" column %s"), get_relid_attribute_name(object->objectId, object->objectSubId)); break; case OCLASS_PROC: - appendStringInfo(&buffer, "function %s", + appendStringInfo(&buffer, gettext("function %s"), format_procedure(object->objectId)); break; case OCLASS_TYPE: - appendStringInfo(&buffer, "type %s", + appendStringInfo(&buffer, gettext("type %s"), format_type_be(object->objectId)); break; @@ -1488,7 +1467,7 @@ getObjectDescription(const ObjectAddress *object) castForm = (Form_pg_cast) GETSTRUCT(tup); - appendStringInfo(&buffer, "cast from %s to %s", + appendStringInfo(&buffer, gettext("cast from %s to %s"), format_type_be(castForm->castsource), format_type_be(castForm->casttarget)); @@ -1525,13 +1504,13 @@ getObjectDescription(const ObjectAddress *object) if (OidIsValid(con->conrelid)) { - appendStringInfo(&buffer, "constraint %s on ", + appendStringInfo(&buffer, gettext("constraint %s on "), NameStr(con->conname)); getRelationDescription(&buffer, con->conrelid); } else { - appendStringInfo(&buffer, "constraint %s", + appendStringInfo(&buffer, gettext("constraint %s"), NameStr(con->conname)); } @@ -1550,7 +1529,7 @@ getObjectDescription(const ObjectAddress *object) if (!HeapTupleIsValid(conTup)) elog(ERROR, "cache lookup failed for conversion %u", object->objectId); - appendStringInfo(&buffer, "conversion %s", + appendStringInfo(&buffer, gettext("conversion %s"), NameStr(((Form_pg_conversion) GETSTRUCT(conTup))->conname)); ReleaseSysCache(conTup); break; @@ -1587,7 +1566,7 @@ getObjectDescription(const ObjectAddress *object) colobject.objectId = attrdef->adrelid; colobject.objectSubId = attrdef->adnum; - appendStringInfo(&buffer, "default for %s", + appendStringInfo(&buffer, gettext("default for %s"), getObjectDescription(&colobject)); systable_endscan(adscan); @@ -1605,14 +1584,14 @@ getObjectDescription(const ObjectAddress *object) if (!HeapTupleIsValid(langTup)) elog(ERROR, "cache lookup failed for language %u", object->objectId); - appendStringInfo(&buffer, "language %s", + appendStringInfo(&buffer, gettext("language %s"), NameStr(((Form_pg_language) GETSTRUCT(langTup))->lanname)); ReleaseSysCache(langTup); break; } case OCLASS_OPERATOR: - appendStringInfo(&buffer, "operator %s", + appendStringInfo(&buffer, gettext("operator %s"), format_operator(object->objectId)); break; @@ -1632,16 +1611,6 @@ getObjectDescription(const ObjectAddress *object) object->objectId); opcForm = (Form_pg_opclass) GETSTRUCT(opcTup); - /* Qualify the name if not visible in search path */ - if (OpclassIsVisible(object->objectId)) - nspname = NULL; - else - nspname = get_namespace_name(opcForm->opcnamespace); - - appendStringInfo(&buffer, "operator class %s", - quote_qualified_identifier(nspname, - NameStr(opcForm->opcname))); - amTup = SearchSysCache(AMOID, ObjectIdGetDatum(opcForm->opcamid), 0, 0, 0); @@ -1650,7 +1619,15 @@ getObjectDescription(const ObjectAddress *object) opcForm->opcamid); amForm = (Form_pg_am) GETSTRUCT(amTup); - appendStringInfo(&buffer, " for %s", + /* Qualify the name if not visible in search path */ + if (OpclassIsVisible(object->objectId)) + nspname = NULL; + else + nspname = get_namespace_name(opcForm->opcnamespace); + + appendStringInfo(&buffer, gettext("operator class %s for %s"), + quote_qualified_identifier(nspname, + NameStr(opcForm->opcname)), NameStr(amForm->amname)); ReleaseSysCache(amTup); @@ -1684,7 +1661,7 @@ getObjectDescription(const ObjectAddress *object) rule = (Form_pg_rewrite) GETSTRUCT(tup); - appendStringInfo(&buffer, "rule %s on ", + appendStringInfo(&buffer, gettext("rule %s on "), NameStr(rule->rulename)); getRelationDescription(&buffer, rule->ev_class); @@ -1719,7 +1696,7 @@ getObjectDescription(const ObjectAddress *object) trig = (Form_pg_trigger) GETSTRUCT(tup); - appendStringInfo(&buffer, "trigger %s on ", + appendStringInfo(&buffer, gettext("trigger %s on "), NameStr(trig->tgname)); getRelationDescription(&buffer, trig->tgrelid); @@ -1736,7 +1713,7 @@ getObjectDescription(const ObjectAddress *object) if (!nspname) elog(ERROR, "cache lookup failed for namespace %u", object->objectId); - appendStringInfo(&buffer, "schema %s", nspname); + appendStringInfo(&buffer, gettext("schema %s"), nspname); break; } @@ -1780,40 +1757,40 @@ getRelationDescription(StringInfo buffer, Oid relid) switch (relForm->relkind) { case RELKIND_RELATION: - appendStringInfo(buffer, "table %s", + appendStringInfo(buffer, gettext("table %s"), relname); break; case RELKIND_INDEX: - appendStringInfo(buffer, "index %s", + appendStringInfo(buffer, gettext("index %s"), relname); break; case RELKIND_SPECIAL: - appendStringInfo(buffer, "special system relation %s", + appendStringInfo(buffer, gettext("special system relation %s"), relname); break; case RELKIND_SEQUENCE: - appendStringInfo(buffer, "sequence %s", + appendStringInfo(buffer, gettext("sequence %s"), relname); break; case RELKIND_UNCATALOGED: - appendStringInfo(buffer, "uncataloged table %s", + appendStringInfo(buffer, gettext("uncataloged table %s"), relname); break; case RELKIND_TOASTVALUE: - appendStringInfo(buffer, "toast table %s", + appendStringInfo(buffer, gettext("toast table %s"), relname); break; case RELKIND_VIEW: - appendStringInfo(buffer, "view %s", + appendStringInfo(buffer, gettext("view %s"), relname); break; case RELKIND_COMPOSITE_TYPE: - appendStringInfo(buffer, "composite type %s", + appendStringInfo(buffer, gettext("composite type %s"), relname); break; default: /* shouldn't get here */ - appendStringInfo(buffer, "relation %s", + appendStringInfo(buffer, gettext("relation %s"), relname); break; } diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 087e2d7f23..51b7d31772 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.262 2004/04/01 21:28:43 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.263 2004/05/05 04:48:45 tgl Exp $ * * * INTERFACE ROUTINES @@ -72,7 +72,6 @@ static void AddNewRelationType(const char *typeName, char new_rel_kind, Oid new_type_oid); static void RelationRemoveInheritance(Relation relation); -static void StoreAttrDefault(Relation rel, AttrNumber attnum, char *adbin); static void StoreRelCheck(Relation rel, char *ccname, char *ccbin); static void StoreConstraints(Relation rel, TupleDesc tupdesc); static void SetRelationNumChecks(Relation rel, int numchecks); @@ -1246,7 +1245,7 @@ heap_drop_with_catalog(Oid rid) * Store a default expression for column attnum of relation rel. * The expression must be presented as a nodeToString() string. */ -static void +void StoreAttrDefault(Relation rel, AttrNumber attnum, char *adbin) { Node *expr; @@ -1483,16 +1482,20 @@ StoreConstraints(Relation rel, TupleDesc tupdesc) * will be processed only if they are CONSTR_CHECK type and contain a "raw" * expression. * + * Returns a list of CookedConstraint nodes that shows the cooked form of + * the default and constraint expressions added to the relation. + * * NB: caller should have opened rel with AccessExclusiveLock, and should * hold that lock till end of transaction. Also, we assume the caller has * done a CommandCounterIncrement if necessary to make the relation's catalog * tuples visible. */ -void +List * AddRelationRawConstraints(Relation rel, List *rawColDefaults, List *rawConstraints) { + List *cookedConstraints = NIL; char *relname = RelationGetRelationName(rel); TupleDesc tupleDesc; TupleConstr *oldconstr; @@ -1504,6 +1507,7 @@ AddRelationRawConstraints(Relation rel, int constr_name_ctr = 0; List *listptr; Node *expr; + CookedConstraint *cooked; /* * Get info about existing constraints. @@ -1544,7 +1548,15 @@ AddRelationRawConstraints(Relation rel, expr = cookDefault(pstate, colDef->raw_default, atp->atttypid, atp->atttypmod, NameStr(atp->attname)); + StoreAttrDefault(rel, colDef->attnum, nodeToString(expr)); + + cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint)); + cooked->contype = CONSTR_DEFAULT; + cooked->name = NULL; + cooked->attnum = colDef->attnum; + cooked->expr = expr; + cookedConstraints = lappend(cookedConstraints, cooked); } /* @@ -1672,6 +1684,13 @@ AddRelationRawConstraints(Relation rel, StoreRelCheck(rel, ccname, nodeToString(expr)); numchecks++; + + cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint)); + cooked->contype = CONSTR_CHECK; + cooked->name = ccname; + cooked->attnum = 0; + cooked->expr = expr; + cookedConstraints = lappend(cookedConstraints, cooked); } /* @@ -1682,6 +1701,8 @@ AddRelationRawConstraints(Relation rel, * (This is critical if we added defaults but not constraints.) */ SetRelationNumChecks(rel, numchecks); + + return cookedConstraints; } /* diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 86076960a4..5a8d5b2267 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.228 2004/02/15 21:01:39 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.229 2004/05/05 04:48:45 tgl Exp $ * * * INTERFACE ROUTINES @@ -470,7 +470,8 @@ index_create(Oid heapRelationId, Oid *classObjectId, bool primary, bool isconstraint, - bool allow_system_table_mods) + bool allow_system_table_mods, + bool skip_build) { Relation heapRelation; Relation indexRelation; @@ -721,21 +722,31 @@ index_create(Oid heapRelationId, * If this is bootstrap (initdb) time, then we don't actually fill in * the index yet. We'll be creating more indexes and classes later, * so we delay filling them in until just before we're done with - * bootstrapping. Otherwise, we call the routine that constructs the - * index. + * bootstrapping. Similarly, if the caller specified skip_build then + * filling the index is delayed till later (ALTER TABLE can save work + * in some cases with this). Otherwise, we call the AM routine that + * constructs the index. * - * In normal processing mode, the heap and index relations are closed by - * index_build() --- but we continue to hold the ShareLock on the heap - * and the exclusive lock on the index that we acquired above, until - * end of transaction. + * In normal processing mode, the heap and index relations are closed, + * but we continue to hold the ShareLock on the heap and the exclusive + * lock on the index that we acquired above, until end of transaction. */ if (IsBootstrapProcessingMode()) { index_register(heapRelationId, indexoid, indexInfo); /* XXX shouldn't we close the heap and index rels here? */ } + else if (skip_build) + { + /* caller is responsible for filling the index later on */ + relation_close(indexRelation, NoLock); + heap_close(heapRelation, NoLock); + } else + { index_build(heapRelation, indexRelation, indexInfo); + /* index_build closes the passed rels */ + } return indexoid; } diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index a10bbbd746..b4bf08006e 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.120 2004/03/23 19:35:16 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.121 2004/05/05 04:48:45 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -64,11 +64,7 @@ typedef struct static void cluster_rel(RelToCluster *rv, bool recheck); -static Oid make_new_heap(Oid OIDOldHeap, const char *NewName); static void copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex); -static List *get_indexattr_list(Relation OldHeap, Oid OldIndex); -static void rebuild_indexes(Oid OIDOldHeap, List *indexes); -static void swap_relfilenodes(Oid r1, Oid r2); static List *get_tables_to_cluster(MemoryContext cluster_context); @@ -479,7 +475,7 @@ rebuild_relation(Relation OldHeap, Oid indexOid) /* * Create the new table that we will fill with correctly-ordered data. */ -static Oid +Oid make_new_heap(Oid OIDOldHeap, const char *NewName) { TupleDesc OldHeapDesc, @@ -578,7 +574,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex) * Get the necessary info about the indexes of the relation and * return a list of IndexAttrs structures. */ -static List * +List * get_indexattr_list(Relation OldHeap, Oid OldIndex) { List *indexes = NIL; @@ -621,7 +617,7 @@ get_indexattr_list(Relation OldHeap, Oid OldIndex) * Create new indexes and swap the filenodes with old indexes. Then drop * the new index (carrying the old index filenode along). */ -static void +void rebuild_indexes(Oid OIDOldHeap, List *indexes) { List *elem; @@ -646,10 +642,15 @@ rebuild_indexes(Oid OIDOldHeap, List *indexes) * matter: after the filenode swap the index will keep the * constraint status of the old index. */ - newIndexOID = index_create(OIDOldHeap, newIndexName, - attrs->indexInfo, attrs->accessMethodOID, - attrs->classOID, false, - false, allowSystemTableMods); + newIndexOID = index_create(OIDOldHeap, + newIndexName, + attrs->indexInfo, + attrs->accessMethodOID, + attrs->classOID, + false, + false, + allowSystemTableMods, + false); CommandCounterIncrement(); /* Swap the filenodes. */ @@ -698,7 +699,7 @@ rebuild_indexes(Oid OIDOldHeap, List *indexes) * Also swap any TOAST links, so that the toast data moves along with * the main-table data. */ -static void +void swap_relfilenodes(Oid r1, Oid r2) { Relation relRelation, @@ -789,9 +790,9 @@ swap_relfilenodes(Oid r1, Oid r2) * their new owning relations. Otherwise the wrong one will get * dropped ... * - * NOTE: for now, we can assume the new table will have a TOAST table if - * and only if the old one does. This logic might need work if we get - * smarter about dropped columns. + * NOTE: it is possible that only one table has a toast table; this + * can happen in CLUSTER if there were dropped columns in the old table, + * and in ALTER TABLE when adding or changing type of columns. * * NOTE: at present, a TOAST table's only dependency is the one on its * owning table. If more are ever created, we'd need to use something @@ -804,35 +805,43 @@ swap_relfilenodes(Oid r1, Oid r2) toastobject; long count; - if (!(relform1->reltoastrelid && relform2->reltoastrelid)) - elog(ERROR, "expected both swapped tables to have TOAST tables"); - /* Delete old dependencies */ - count = deleteDependencyRecordsFor(RelOid_pg_class, - relform1->reltoastrelid); - if (count != 1) - elog(ERROR, "expected one dependency record for TOAST table, found %ld", - count); - count = deleteDependencyRecordsFor(RelOid_pg_class, - relform2->reltoastrelid); - if (count != 1) - elog(ERROR, "expected one dependency record for TOAST table, found %ld", - count); + if (relform1->reltoastrelid) + { + count = deleteDependencyRecordsFor(RelOid_pg_class, + relform1->reltoastrelid); + if (count != 1) + elog(ERROR, "expected one dependency record for TOAST table, found %ld", + count); + } + if (relform2->reltoastrelid) + { + count = deleteDependencyRecordsFor(RelOid_pg_class, + relform2->reltoastrelid); + if (count != 1) + elog(ERROR, "expected one dependency record for TOAST table, found %ld", + count); + } /* Register new dependencies */ baseobject.classId = RelOid_pg_class; - baseobject.objectId = r1; baseobject.objectSubId = 0; toastobject.classId = RelOid_pg_class; - toastobject.objectId = relform1->reltoastrelid; toastobject.objectSubId = 0; - recordDependencyOn(&toastobject, &baseobject, DEPENDENCY_INTERNAL); + if (relform1->reltoastrelid) + { + baseobject.objectId = r1; + toastobject.objectId = relform1->reltoastrelid; + recordDependencyOn(&toastobject, &baseobject, DEPENDENCY_INTERNAL); + } - baseobject.objectId = r2; - toastobject.objectId = relform2->reltoastrelid; - - recordDependencyOn(&toastobject, &baseobject, DEPENDENCY_INTERNAL); + if (relform2->reltoastrelid) + { + baseobject.objectId = r2; + toastobject.objectId = relform2->reltoastrelid; + recordDependencyOn(&toastobject, &baseobject, DEPENDENCY_INTERNAL); + } } /* diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 507bcf50d5..efcc70c685 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.117 2003/12/28 21:57:36 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.118 2004/05/05 04:48:45 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -31,6 +31,7 @@ #include "miscadmin.h" #include "optimizer/clauses.h" #include "optimizer/prep.h" +#include "parser/analyze.h" #include "parser/parsetree.h" #include "parser/parse_coerce.h" #include "parser/parse_expr.h" @@ -38,38 +39,62 @@ #include "utils/acl.h" #include "utils/builtins.h" #include "utils/lsyscache.h" +#include "utils/relcache.h" #include "utils/syscache.h" /* non-export function prototypes */ static void CheckPredicate(Expr *predicate); static void ComputeIndexAttrs(IndexInfo *indexInfo, Oid *classOidP, - List *attList, - Oid relId, - char *accessMethodName, Oid accessMethodId); + List *attList, + Oid relId, + char *accessMethodName, Oid accessMethodId, + bool isconstraint); static Oid GetIndexOpClass(List *opclass, Oid attrType, char *accessMethodName, Oid accessMethodId); static Oid GetDefaultOpClass(Oid attrType, Oid accessMethodId); +static char *CreateIndexName(const char *table_name, const char *column_name, + const char *label, Oid inamespace); +static bool relationHasPrimaryKey(Relation rel); + /* * DefineIndex * Creates a new index. * - * 'attributeList' is a list of IndexElem specifying columns and expressions + * 'heapRelation': the relation the index will apply to. + * 'indexRelationName': the name for the new index, or NULL to indicate + * that a nonconflicting default name should be picked. + * 'accessMethodName': name of the AM to use. + * 'attributeList': a list of IndexElem specifying columns and expressions * to index on. - * 'predicate' is the qual specified in the where clause. - * 'rangetable' is needed to interpret the predicate. + * 'predicate': the partial-index condition, or NULL if none. + * 'rangetable': needed to interpret the predicate. + * 'unique': make the index enforce uniqueness. + * 'primary': mark the index as a primary key in the catalogs. + * 'isconstraint': index is for a PRIMARY KEY or UNIQUE constraint, + * so build a pg_constraint entry for it. + * 'is_alter_table': this is due to an ALTER rather than a CREATE operation. + * 'check_rights': check for CREATE rights in the namespace. (This should + * be true except when ALTER is deleting/recreating an index.) + * 'skip_build': make the catalog entries but leave the index file empty; + * it will be filled later. + * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints. */ void DefineIndex(RangeVar *heapRelation, char *indexRelationName, char *accessMethodName, List *attributeList, + Expr *predicate, + List *rangetable, bool unique, bool primary, bool isconstraint, - Expr *predicate, - List *rangetable) + bool is_alter_table, + bool check_rights, + bool skip_build, + bool quiet) { Oid *classObjectId; Oid accessMethodId; @@ -111,15 +136,13 @@ DefineIndex(RangeVar *heapRelation, relationId = RelationGetRelid(rel); namespaceId = RelationGetNamespace(rel); - heap_close(rel, NoLock); - /* * Verify we (still) have CREATE rights in the rel's namespace. * (Presumably we did when the rel was created, but maybe not - * anymore.) Skip check if bootstrapping, since permissions machinery - * may not be working yet. + * anymore.) Skip check if caller doesn't want it. Also skip check + * if bootstrapping, since permissions machinery may not be working yet. */ - if (!IsBootstrapProcessingMode()) + if (check_rights && !IsBootstrapProcessingMode()) { AclResult aclresult; @@ -130,6 +153,27 @@ DefineIndex(RangeVar *heapRelation, get_namespace_name(namespaceId)); } + /* + * Select name for index if caller didn't specify + */ + if (indexRelationName == NULL) + { + if (primary) + indexRelationName = CreateIndexName(RelationGetRelationName(rel), + NULL, + "pkey", + namespaceId); + else + { + IndexElem *iparam = (IndexElem *) lfirst(attributeList); + + indexRelationName = CreateIndexName(RelationGetRelationName(rel), + iparam->name, + "key", + namespaceId); + } + } + /* * look up the access method, verify it can handle the requested * features @@ -177,13 +221,33 @@ DefineIndex(RangeVar *heapRelation, CheckPredicate(predicate); /* - * Check that all of the attributes in a primary key are marked as not - * null, otherwise attempt to ALTER TABLE .. SET NOT NULL + * Extra checks when creating a PRIMARY KEY index. */ if (primary) { + List *cmds; List *keys; + /* + * If ALTER TABLE, check that there isn't already a PRIMARY KEY. + * In CREATE TABLE, we have faith that the parser rejected multiple + * pkey clauses; and CREATE INDEX doesn't have a way to say + * PRIMARY KEY, so it's no problem either. + */ + if (is_alter_table && + relationHasPrimaryKey(rel)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("multiple primary keys for table \"%s\" are not allowed", + RelationGetRelationName(rel)))); + } + + /* + * Check that all of the attributes in a primary key are marked as not + * null, otherwise attempt to ALTER TABLE .. SET NOT NULL + */ + cmds = NIL; foreach(keys, attributeList) { IndexElem *key = (IndexElem *) lfirst(keys); @@ -203,29 +267,43 @@ DefineIndex(RangeVar *heapRelation, { if (!((Form_pg_attribute) GETSTRUCT(atttuple))->attnotnull) { - /* - * Try to make it NOT NULL. - * - * XXX: Shouldn't the ALTER TABLE .. SET NOT NULL cascade - * to child tables? Currently, since the PRIMARY KEY - * itself doesn't cascade, we don't cascade the - * notnull constraint either; but this is pretty - * debatable. - */ - AlterTableAlterColumnSetNotNull(relationId, false, - key->name); + /* Add a subcommand to make this one NOT NULL */ + AlterTableCmd *cmd = makeNode(AlterTableCmd); + + cmd->subtype = AT_SetNotNull; + cmd->name = key->name; + + cmds = lappend(cmds, cmd); } ReleaseSysCache(atttuple); } else { - /* This shouldn't happen if parser did its job ... */ + /* + * This shouldn't happen during CREATE TABLE, but can + * happen during ALTER TABLE. Keep message in sync with + * transformIndexConstraints() in parser/analyze.c. + */ ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("column \"%s\" named in key does not exist", key->name))); } } + + /* + * XXX: Shouldn't the ALTER TABLE .. SET NOT NULL cascade + * to child tables? Currently, since the PRIMARY KEY + * itself doesn't cascade, we don't cascade the + * notnull constraint(s) either; but this is pretty debatable. + * + * XXX: possible future improvement: when being called from + * ALTER TABLE, it would be more efficient to merge this with + * the outer ALTER TABLE, so as to avoid two scans. But that + * seems to complicate DefineIndex's API unduly. + */ + if (cmds) + AlterTableInternal(relationId, cmds, false); } /* @@ -242,11 +320,26 @@ DefineIndex(RangeVar *heapRelation, classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid)); ComputeIndexAttrs(indexInfo, classObjectId, attributeList, - relationId, accessMethodName, accessMethodId); + relationId, accessMethodName, accessMethodId, + isconstraint); + + heap_close(rel, NoLock); + + /* + * Report index creation if appropriate (delay this till after most + * of the error checks) + */ + if (isconstraint && !quiet) + ereport(NOTICE, + (errmsg("%s %s will create implicit index \"%s\" for table \"%s\"", + is_alter_table ? "ALTER TABLE / ADD" : "CREATE TABLE /", + primary ? "PRIMARY KEY" : "UNIQUE", + indexRelationName, RelationGetRelationName(rel)))); index_create(relationId, indexRelationName, indexInfo, accessMethodId, classObjectId, - primary, isconstraint, allowSystemTableMods); + primary, isconstraint, + allowSystemTableMods, skip_build); /* * We update the relation's pg_class tuple even if it already has @@ -303,7 +396,8 @@ ComputeIndexAttrs(IndexInfo *indexInfo, List *attList, /* list of IndexElem's */ Oid relId, char *accessMethodName, - Oid accessMethodId) + Oid accessMethodId, + bool isconstraint) { List *rest; int attn = 0; @@ -325,10 +419,19 @@ ComputeIndexAttrs(IndexInfo *indexInfo, Assert(attribute->expr == NULL); atttuple = SearchSysCacheAttName(relId, attribute->name); if (!HeapTupleIsValid(atttuple)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("column \"%s\" does not exist", - attribute->name))); + { + /* difference in error message spellings is historical */ + if (isconstraint) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" named in key does not exist", + attribute->name))); + else + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" does not exist", + attribute->name))); + } attform = (Form_pg_attribute) GETSTRUCT(atttuple); indexInfo->ii_KeyAttrNumbers[attn] = attform->attnum; atttype = attform->atttypid; @@ -553,6 +656,79 @@ GetDefaultOpClass(Oid attrType, Oid accessMethodId) return InvalidOid; } +/* + * Select a nonconflicting name for an index. + */ +static char * +CreateIndexName(const char *table_name, const char *column_name, + const char *label, Oid inamespace) +{ + int pass = 0; + char *iname = NULL; + char typename[NAMEDATALEN]; + + /* + * The type name for makeObjectName is label, or labelN if that's + * necessary to prevent collision with existing indexes. + */ + strncpy(typename, label, sizeof(typename)); + + for (;;) + { + iname = makeObjectName(table_name, column_name, typename); + + if (!OidIsValid(get_relname_relid(iname, inamespace))) + break; + + /* found a conflict, so try a new name component */ + pfree(iname); + snprintf(typename, sizeof(typename), "%s%d", label, ++pass); + } + + return iname; +} + +/* + * relationHasPrimaryKey - + * + * See whether an existing relation has a primary key. + */ +static bool +relationHasPrimaryKey(Relation rel) +{ + bool result = false; + List *indexoidlist, + *indexoidscan; + + /* + * Get the list of index OIDs for the table from the relcache, and + * look up each one in the pg_index syscache until we find one marked + * primary key (hopefully there isn't more than one such). + */ + indexoidlist = RelationGetIndexList(rel); + + foreach(indexoidscan, indexoidlist) + { + Oid indexoid = lfirsto(indexoidscan); + HeapTuple indexTuple; + + indexTuple = SearchSysCache(INDEXRELID, + ObjectIdGetDatum(indexoid), + 0, 0, 0); + if (!HeapTupleIsValid(indexTuple)) /* should not happen */ + elog(ERROR, "cache lookup failed for index %u", indexoid); + result = ((Form_pg_index) GETSTRUCT(indexTuple))->indisprimary; + ReleaseSysCache(indexTuple); + if (result) + break; + } + + freeList(indexoidlist); + + return result; +} + + /* * RemoveIndex * Deletes an index. diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index a6e3a93d34..a9c307b80c 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.102 2004/04/01 21:28:44 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.103 2004/05/05 04:48:45 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -24,27 +24,33 @@ #include "catalog/indexing.h" #include "catalog/namespace.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_depend.h" #include "catalog/pg_inherits.h" #include "catalog/pg_namespace.h" #include "catalog/pg_opclass.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" #include "commands/cluster.h" +#include "commands/defrem.h" #include "commands/tablecmds.h" #include "commands/trigger.h" #include "executor/executor.h" +#include "lib/stringinfo.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "optimizer/clauses.h" #include "optimizer/plancat.h" #include "optimizer/prep.h" +#include "parser/analyze.h" #include "parser/gramparse.h" +#include "parser/parser.h" #include "parser/parse_clause.h" #include "parser/parse_coerce.h" #include "parser/parse_expr.h" #include "parser/parse_oper.h" #include "parser/parse_relation.h" #include "parser/parse_type.h" +#include "rewrite/rewriteHandler.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" @@ -76,6 +82,83 @@ typedef struct OnCommitItem static List *on_commits = NIL; +/* + * State information for ALTER TABLE + * + * The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo + * structs, one for each table modified by the operation (the named table + * plus any child tables that are affected). We save lists of subcommands + * to apply to this table (possibly modified by parse transformation steps); + * these lists will be executed in Phase 2. If a Phase 3 step is needed, + * necessary information is stored in the constraints and newvals lists. + * + * Phase 2 is divided into multiple passes; subcommands are executed in + * a pass determined by subcommand type. + */ + +#define AT_PASS_DROP 0 /* DROP (all flavors) */ +#define AT_PASS_ALTER_TYPE 1 /* ALTER COLUMN TYPE */ +#define AT_PASS_OLD_INDEX 2 /* re-add existing indexes */ +#define AT_PASS_OLD_CONSTR 3 /* re-add existing constraints */ +#define AT_PASS_COL_ATTRS 4 /* set other column attributes */ +/* We could support a RENAME COLUMN pass here, but not currently used */ +#define AT_PASS_ADD_COL 5 /* ADD COLUMN */ +#define AT_PASS_ADD_INDEX 6 /* ADD indexes */ +#define AT_PASS_ADD_CONSTR 7 /* ADD constraints, defaults */ +#define AT_PASS_MISC 8 /* other stuff */ +#define AT_NUM_PASSES 9 + +typedef struct AlteredTableInfo +{ + /* Information saved before any work commences: */ + Oid relid; /* Relation to work on */ + TupleDesc oldDesc; /* Pre-modification tuple descriptor */ + /* Information saved by Phase 1 for Phase 2: */ + List *subcmds[AT_NUM_PASSES]; /* Lists of AlterTableCmd */ + /* Information saved by Phases 1/2 for Phase 3: */ + List *constraints; /* List of NewConstraint */ + List *newvals; /* List of NewColumnValue */ + /* Objects to rebuild after completing ALTER TYPE operations */ + List *changedConstraintOids; /* OIDs of constraints to rebuild */ + List *changedConstraintDefs; /* string definitions of same */ + List *changedIndexOids; /* OIDs of indexes to rebuild */ + List *changedIndexDefs; /* string definitions of same */ + /* Workspace for ATExecAddConstraint */ + int constr_name_ctr; +} AlteredTableInfo; + +/* Struct describing one new constraint to check in Phase 3 scan */ +typedef struct NewConstraint +{ + char *name; /* Constraint name, or NULL if none */ + ConstrType contype; /* CHECK, NOT_NULL, or FOREIGN */ + AttrNumber attnum; /* only relevant for NOT_NULL */ + Oid refrelid; /* PK rel, if FOREIGN */ + Node *qual; /* Check expr or FkConstraint struct */ + List *qualstate; /* Execution state for CHECK */ +} NewConstraint; + +/* + * Struct describing one new column value that needs to be computed during + * Phase 3 copy (this could be either a new column with a non-null default, or + * a column that we're changing the type of). Columns without such an entry + * are just copied from the old table during ATRewriteTable. Note that the + * expr is an expression over *old* table values. + */ +typedef struct NewColumnValue +{ + AttrNumber attnum; /* which column */ + Expr *expr; /* expression to compute */ + ExprState *exprstate; /* execution state */ +} NewColumnValue; + + +/* Used by attribute and relation renaming routines: */ +#define RI_TRIGGER_PK 1 /* is a trigger on the PK relation */ +#define RI_TRIGGER_FK 2 /* is a trigger on the FK relation */ +#define RI_TRIGGER_NONE 0 /* is not an RI trigger function */ + + static List *MergeAttributes(List *schema, List *supers, bool istemp, List **supOids, List **supconstr, int *supOidCount); static bool change_varattnos_of_a_node(Node *node, const AttrNumber *newattno); @@ -83,9 +166,6 @@ static void StoreCatalogInheritance(Oid relationId, List *supers); static int findAttrByName(const char *attributeName, List *schema); static void setRelhassubclassInRelation(Oid relationId, bool relhassubclass); static bool needs_toast_table(Relation rel); -static void AlterTableAddCheckConstraint(Relation rel, Constraint *constr); -static void AlterTableAddForeignKeyConstraint(Relation rel, - FkConstraint *fkconstraint); static int transformColumnNameList(Oid relId, List *colList, int16 *attnums, Oid *atttypids); static int transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid, @@ -100,13 +180,58 @@ static void validateForeignKeyConstraint(FkConstraint *fkconstraint, static void createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint, Oid constrOid); static char *fkMatchTypeToString(char match_type); - -/* Used by attribute and relation renaming routines: */ - -#define RI_TRIGGER_PK 1 /* is a trigger on the PK relation */ -#define RI_TRIGGER_FK 2 /* is a trigger on the FK relation */ -#define RI_TRIGGER_NONE 0 /* is not an RI trigger function */ - +static void ATController(Relation rel, List *cmds, bool recurse); +static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, + bool recurse, bool recursing); +static void ATRewriteCatalogs(List **wqueue); +static void ATExecCmd(AlteredTableInfo *tab, Relation rel, AlterTableCmd *cmd); +static void ATRewriteTables(List **wqueue); +static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap); +static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel); +static void ATSimplePermissions(Relation rel, bool allowView); +static void ATSimpleRecursion(List **wqueue, Relation rel, + AlterTableCmd *cmd, bool recurse); +static void ATOneLevelRecursion(List **wqueue, Relation rel, + AlterTableCmd *cmd); +static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, + AlterTableCmd *cmd); +static void ATExecAddColumn(AlteredTableInfo *tab, Relation rel, + ColumnDef *colDef); +static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid); +static void ATExecDropNotNull(Relation rel, const char *colName); +static void ATExecSetNotNull(AlteredTableInfo *tab, Relation rel, + const char *colName); +static void ATExecColumnDefault(Relation rel, const char *colName, + Node *newDefault); +static void ATPrepSetStatistics(Relation rel, const char *colName, + Node *flagValue); +static void ATExecSetStatistics(Relation rel, const char *colName, + Node *newValue); +static void ATExecSetStorage(Relation rel, const char *colName, + Node *newValue); +static void ATExecDropColumn(Relation rel, const char *colName, + DropBehavior behavior, + bool recurse, bool recursing); +static void ATExecAddIndex(AlteredTableInfo *tab, Relation rel, + IndexStmt *stmt, bool is_rebuild); +static void ATExecAddConstraint(AlteredTableInfo *tab, Relation rel, + Node *newConstraint); +static void ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, + FkConstraint *fkconstraint); +static void ATPrepDropConstraint(List **wqueue, Relation rel, + bool recurse, AlterTableCmd *cmd); +static void ATExecDropConstraint(Relation rel, const char *constrName, + DropBehavior behavior, bool quiet); +static void ATPrepAlterColumnType(List **wqueue, + AlteredTableInfo *tab, Relation rel, + bool recurse, bool recursing, + AlterTableCmd *cmd); +static void ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, + const char *colName, TypeName *typename); +static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab); +static void ATPostAlterTypeParse(char *cmd, List **wqueue); +static void ATExecChangeOwner(Oid relationOid, int32 newOwnerSysId); +static void ATExecClusterOn(Relation rel, const char *indexName); static int ri_trigger_type(Oid tgfoid); static void update_ri_trigger_args(Oid relid, const char *oldname, @@ -1100,7 +1225,7 @@ renameatt(Oid myrelid, if (childrelid == myrelid) continue; - /* note we need not recurse again! */ + /* note we need not recurse again */ renameatt(childrelid, oldattname, newattname, false, true); } } @@ -1129,7 +1254,7 @@ renameatt(Oid myrelid, attform = (Form_pg_attribute) GETSTRUCT(atttup); attnum = attform->attnum; - if (attnum < 0) + if (attnum <= 0) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot rename system column \"%s\"", @@ -1240,8 +1365,7 @@ renameatt(Oid myrelid, true, false); } - relation_close(targetrelation, NoLock); /* close rel but keep - * lock! */ + relation_close(targetrelation, NoLock); /* close rel but keep lock */ } /* @@ -1559,55 +1683,803 @@ update_ri_trigger_args(Oid relid, CommandCounterIncrement(); } - -/* ---------------- - * AlterTableAddColumn - * (formerly known as PerformAddAttribute) +/* + * AlterTable + * Execute ALTER TABLE, which can be a list of subcommands * - * adds an additional attribute to a relation - * ---------------- + * ALTER TABLE is performed in three phases: + * 1. Examine subcommands and perform pre-transformation checking. + * 2. Update system catalogs. + * 3. Scan table(s) to check new constraints, and optionally recopy + * the data into new table(s). + * Phase 3 is not performed unless one or more of the subcommands requires + * it. The intention of this design is to allow multiple independent + * updates of the table schema to be performed with only one pass over the + * data. + * + * ATPrepCmd performs phase 1. A "work queue" entry is created for + * each table to be affected (there may be multiple affected tables if the + * commands traverse a table inheritance hierarchy). Also we do preliminary + * validation of the subcommands, including parse transformation of those + * expressions that need to be evaluated with respect to the old table + * schema. + * + * ATRewriteCatalogs performs phase 2 for each affected table (note that + * phases 2 and 3 do no explicit recursion, since phase 1 already did it). + * Certain subcommands need to be performed before others to avoid + * unnecessary conflicts; for example, DROP COLUMN should come before + * ADD COLUMN. Therefore phase 1 divides the subcommands into multiple + * lists, one for each logical "pass" of phase 2. + * + * ATRewriteTables performs phase 3 for those tables that need it. + * + * Thanks to the magic of MVCC, an error anywhere along the way rolls back + * the whole operation; we don't have to do anything special to clean up. */ void -AlterTableAddColumn(Oid myrelid, - bool recurse, - ColumnDef *colDef) +AlterTable(AlterTableStmt *stmt) { - Relation rel, - pgclass, - attrdesc; - HeapTuple reltup; - HeapTuple newreltup; - HeapTuple attributeTuple; - Form_pg_attribute attribute; - FormData_pg_attribute attributeD; + ATController(relation_openrv(stmt->relation, AccessExclusiveLock), + stmt->cmds, + interpretInhOption(stmt->relation->inhOpt)); +} + +/* + * AlterTableInternal + * + * ALTER TABLE with target specified by OID + */ +void +AlterTableInternal(Oid relid, List *cmds, bool recurse) +{ + ATController(relation_open(relid, AccessExclusiveLock), + cmds, + recurse); +} + +static void +ATController(Relation rel, List *cmds, bool recurse) +{ + List *wqueue = NIL; + List *lcmd; + + /* Phase 1: preliminary examination of commands, create work queue */ + foreach(lcmd, cmds) + { + AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd); + + ATPrepCmd(&wqueue, rel, cmd, recurse, false); + } + + /* Close the relation, but keep lock until commit */ + relation_close(rel, NoLock); + + /* Phase 2: update system catalogs */ + ATRewriteCatalogs(&wqueue); + + /* Phase 3: scan/rewrite tables as needed */ + ATRewriteTables(&wqueue); +} + +/* + * ATPrepCmd + * + * Traffic cop for ALTER TABLE Phase 1 operations, including simple + * recursion and permission checks. + * + * Caller must have acquired AccessExclusiveLock on relation already. + * This lock should be held until commit. + */ +static void +ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, + bool recurse, bool recursing) +{ + AlteredTableInfo *tab; + int pass; + + /* Find or create work queue entry for this table */ + tab = ATGetQueueEntry(wqueue, rel); + + /* + * Copy the original subcommand for each table. This avoids conflicts + * when different child tables need to make different parse + * transformations (for example, the same column may have different + * column numbers in different children). + */ + cmd = copyObject(cmd); + + /* + * Do permissions checking, recursion to child tables if needed, + * and any additional phase-1 processing needed. + */ + switch (cmd->subtype) + { + case AT_AddColumn: /* ADD COLUMN */ + ATSimplePermissions(rel, false); + /* Performs own recursion */ + ATPrepAddColumn(wqueue, rel, recurse, cmd); + pass = AT_PASS_ADD_COL; + break; + case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */ + /* + * 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. + */ + ATSimplePermissions(rel, true); + ATSimpleRecursion(wqueue, rel, cmd, recurse); + /* No command-specific prep needed */ + pass = AT_PASS_ADD_CONSTR; + break; + case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */ + ATSimplePermissions(rel, false); + ATSimpleRecursion(wqueue, rel, cmd, recurse); + /* No command-specific prep needed */ + pass = AT_PASS_DROP; + break; + case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */ + ATSimplePermissions(rel, false); + ATSimpleRecursion(wqueue, rel, cmd, recurse); + /* No command-specific prep needed */ + pass = AT_PASS_ADD_CONSTR; + break; + case AT_SetStatistics: /* ALTER COLUMN STATISTICS */ + ATSimpleRecursion(wqueue, rel, cmd, recurse); + /* Performs own permission checks */ + ATPrepSetStatistics(rel, cmd->name, cmd->def); + pass = AT_PASS_COL_ATTRS; + break; + case AT_SetStorage: /* ALTER COLUMN STORAGE */ + ATSimplePermissions(rel, false); + ATSimpleRecursion(wqueue, rel, cmd, recurse); + /* No command-specific prep needed */ + pass = AT_PASS_COL_ATTRS; + break; + case AT_DropColumn: /* DROP COLUMN */ + ATSimplePermissions(rel, false); + /* Recursion occurs during execution phase */ + /* No command-specific prep needed except saving recurse flag */ + if (recurse) + cmd->subtype = AT_DropColumnRecurse; + pass = AT_PASS_DROP; + break; + case AT_AddIndex: /* ADD INDEX */ + ATSimplePermissions(rel, false); + /* This command never recurses */ + /* No command-specific prep needed */ + pass = AT_PASS_ADD_INDEX; + break; + case AT_AddConstraint: /* ADD CONSTRAINT */ + ATSimplePermissions(rel, false); + /* + * Currently we recurse only for CHECK constraints, never for + * foreign-key constraints. UNIQUE/PKEY constraints won't be + * seen here. + */ + if (IsA(cmd->def, Constraint)) + ATSimpleRecursion(wqueue, rel, cmd, recurse); + /* No command-specific prep needed */ + pass = AT_PASS_ADD_CONSTR; + break; + case AT_DropConstraint: /* DROP CONSTRAINT */ + ATSimplePermissions(rel, false); + /* Performs own recursion */ + ATPrepDropConstraint(wqueue, rel, recurse, cmd); + pass = AT_PASS_DROP; + break; + case AT_DropConstraintQuietly: /* DROP CONSTRAINT for child */ + ATSimplePermissions(rel, false); + ATSimpleRecursion(wqueue, rel, cmd, recurse); + /* No command-specific prep needed */ + pass = AT_PASS_DROP; + break; + case AT_AlterColumnType: /* ALTER COLUMN TYPE */ + ATSimplePermissions(rel, false); + /* Performs own recursion */ + ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd); + pass = AT_PASS_ALTER_TYPE; + break; + case AT_ToastTable: /* CREATE TOAST TABLE */ + ATSimplePermissions(rel, false); + /* This command never recurses */ + /* No command-specific prep needed */ + pass = AT_PASS_MISC; + break; + case AT_ChangeOwner: /* ALTER OWNER */ + /* check that we are the superuser */ + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to alter owner"))); + /* This command never recurses */ + /* No command-specific prep needed */ + pass = AT_PASS_MISC; + break; + case AT_ClusterOn: /* CLUSTER ON */ + ATSimplePermissions(rel, false); + /* This command never recurses */ + /* No command-specific prep needed */ + pass = AT_PASS_MISC; + break; + case AT_DropOids: /* SET WITHOUT OIDS */ + ATSimplePermissions(rel, false); + /* Performs own recursion */ + if (rel->rd_rel->relhasoids) + { + AlterTableCmd *dropCmd = makeNode(AlterTableCmd); + + dropCmd->subtype = AT_DropColumn; + dropCmd->name = pstrdup("oid"); + dropCmd->behavior = cmd->behavior; + ATPrepCmd(wqueue, rel, dropCmd, recurse, false); + } + pass = AT_PASS_DROP; + break; + default: /* oops */ + elog(ERROR, "unrecognized alter table type: %d", + (int) cmd->subtype); + pass = 0; /* keep compiler quiet */ + break; + } + + /* Add the subcommand to the appropriate list for phase 2 */ + tab->subcmds[pass] = lappend(tab->subcmds[pass], cmd); +} + +/* + * ATRewriteCatalogs + * + * Traffic cop for ALTER TABLE Phase 2 operations. Subcommands are + * dispatched in a "safe" execution order (designed to avoid unnecessary + * conflicts). + */ +static void +ATRewriteCatalogs(List **wqueue) +{ + int pass; + List *ltab; + + /* + * We process all the tables "in parallel", one pass at a time. This + * is needed because we may have to propagate work from one table + * to another (specifically, ALTER TYPE on a foreign key's PK has to + * dispatch the re-adding of the foreign key constraint to the other + * table). Work can only be propagated into later passes, however. + */ + for (pass = 0; pass < AT_NUM_PASSES; pass++) + { + /* Go through each table that needs to be processed */ + foreach(ltab, *wqueue) + { + AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab); + List *subcmds = tab->subcmds[pass]; + Relation rel; + List *lcmd; + + if (subcmds == NIL) + continue; + + /* Exclusive lock was obtained by phase 1, needn't get it again */ + rel = relation_open(tab->relid, NoLock); + + foreach(lcmd, subcmds) + { + ATExecCmd(tab, rel, (AlterTableCmd *) lfirst(lcmd)); + } + + /* + * After the ALTER TYPE pass, do cleanup work (this is not done in + * ATExecAlterColumnType since it should be done only once if + * multiple columns of a table are altered). + */ + if (pass == AT_PASS_ALTER_TYPE) + ATPostAlterTypeCleanup(wqueue, tab); + + relation_close(rel, NoLock); + } + } + + /* + * Do an implicit CREATE TOAST TABLE if we executed any subcommands + * that might have added a column or changed column storage. + */ + foreach(ltab, *wqueue) + { + AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab); + + if (tab->subcmds[AT_PASS_ADD_COL] || + tab->subcmds[AT_PASS_ALTER_TYPE] || + tab->subcmds[AT_PASS_COL_ATTRS]) + { + AlterTableCreateToastTable(tab->relid, true); + } + } +} + +/* + * ATExecCmd: dispatch a subcommand to appropriate execution routine + */ +static void +ATExecCmd(AlteredTableInfo *tab, Relation rel, AlterTableCmd *cmd) +{ + switch (cmd->subtype) + { + case AT_AddColumn: /* ADD COLUMN */ + ATExecAddColumn(tab, rel, (ColumnDef *) cmd->def); + break; + case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */ + ATExecColumnDefault(rel, cmd->name, cmd->def); + break; + case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */ + ATExecDropNotNull(rel, cmd->name); + break; + case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */ + ATExecSetNotNull(tab, rel, cmd->name); + break; + case AT_SetStatistics: /* ALTER COLUMN STATISTICS */ + ATExecSetStatistics(rel, cmd->name, cmd->def); + break; + case AT_SetStorage: /* ALTER COLUMN STORAGE */ + ATExecSetStorage(rel, cmd->name, cmd->def); + break; + case AT_DropColumn: /* DROP COLUMN */ + ATExecDropColumn(rel, cmd->name, cmd->behavior, false, false); + break; + case AT_DropColumnRecurse: /* DROP COLUMN with recursion */ + ATExecDropColumn(rel, cmd->name, cmd->behavior, true, false); + break; + case AT_AddIndex: /* ADD INDEX */ + ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false); + break; + case AT_ReAddIndex: /* ADD INDEX */ + ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true); + break; + case AT_AddConstraint: /* ADD CONSTRAINT */ + ATExecAddConstraint(tab, rel, cmd->def); + break; + case AT_DropConstraint: /* DROP CONSTRAINT */ + ATExecDropConstraint(rel, cmd->name, cmd->behavior, false); + break; + case AT_DropConstraintQuietly: /* DROP CONSTRAINT for child */ + ATExecDropConstraint(rel, cmd->name, cmd->behavior, true); + break; + case AT_AlterColumnType: /* ALTER COLUMN TYPE */ + ATExecAlterColumnType(tab, rel, cmd->name, (TypeName *) cmd->def); + break; + case AT_ToastTable: /* CREATE TOAST TABLE */ + AlterTableCreateToastTable(RelationGetRelid(rel), false); + break; + case AT_ChangeOwner: /* ALTER OWNER */ + /* get_usesysid raises an error if no such user */ + ATExecChangeOwner(RelationGetRelid(rel), get_usesysid(cmd->name)); + break; + case AT_ClusterOn: /* CLUSTER ON */ + ATExecClusterOn(rel, cmd->name); + break; + case AT_DropOids: /* SET WITHOUT OIDS */ + /* + * Nothing to do here; we'll have generated a DropColumn subcommand + * to do the real work + */ + break; + default: /* oops */ + elog(ERROR, "unrecognized alter table type: %d", + (int) cmd->subtype); + break; + } + + /* + * Bump the command counter to ensure the next subcommand in the sequence + * can see the changes so far + */ + CommandCounterIncrement(); +} + +/* + * ATRewriteTables: ALTER TABLE phase 3 + */ +static void +ATRewriteTables(List **wqueue) +{ + List *ltab; + + /* Go through each table that needs to be checked or rewritten */ + foreach(ltab, *wqueue) + { + AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab); + + /* + * We only need to rewrite the table if at least one column needs + * to be recomputed. + */ + if (tab->newvals != NIL) + { + /* Build a temporary relation and copy data */ + Oid OIDNewHeap; + char NewHeapName[NAMEDATALEN]; + List *indexes; + Relation OldHeap; + ObjectAddress object; + + /* Save the information about all indexes on the relation. */ + OldHeap = heap_open(tab->relid, NoLock); + indexes = get_indexattr_list(OldHeap, InvalidOid); + heap_close(OldHeap, NoLock); + + /* + * Create the new heap, using a temporary name in the same + * namespace as the existing table. NOTE: there is some risk of + * collision with user relnames. Working around this seems more + * trouble than it's worth; in particular, we can't create the new + * heap in a different namespace from the old, or we will have + * problems with the TEMP status of temp tables. + */ + snprintf(NewHeapName, sizeof(NewHeapName), + "pg_temp_%u", tab->relid); + + OIDNewHeap = make_new_heap(tab->relid, NewHeapName); + + /* + * Copy the heap data into the new table with the desired + * modifications, and test the current data within the table + * against new constraints generated by ALTER TABLE commands. + */ + ATRewriteTable(tab, OIDNewHeap); + + /* Swap the relfilenodes of the old and new heaps. */ + swap_relfilenodes(tab->relid, OIDNewHeap); + + CommandCounterIncrement(); + + /* Destroy new heap with old filenode */ + object.classId = RelOid_pg_class; + object.objectId = OIDNewHeap; + object.objectSubId = 0; + + /* + * The new relation is local to our transaction and we know nothing + * depends on it, so DROP_RESTRICT should be OK. + */ + performDeletion(&object, DROP_RESTRICT); + /* performDeletion does CommandCounterIncrement at end */ + + /* + * Rebuild each index on the relation. We do not need + * CommandCounterIncrement() because rebuild_indexes does it. + */ + rebuild_indexes(tab->relid, indexes); + } + else + { + /* + * Test the current data within the table against new constraints + * generated by ALTER TABLE commands, but don't rebuild data. + */ + ATRewriteTable(tab, InvalidOid); + } + } + + /* + * Foreign key constraints are checked in a final pass, since + * (a) it's generally best to examine each one separately, and + * (b) it's at least theoretically possible that we have changed + * both relations of the foreign key, and we'd better have finished + * both rewrites before we try to read the tables. + */ + foreach(ltab, *wqueue) + { + AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab); + Relation rel = NULL; + List *lcon; + + foreach(lcon, tab->constraints) + { + NewConstraint *con = lfirst(lcon); + + if (con->contype == CONSTR_FOREIGN) + { + FkConstraint *fkconstraint = (FkConstraint *) con->qual; + Relation refrel; + + if (rel == NULL) + { + /* Long since locked, no need for another */ + rel = heap_open(tab->relid, NoLock); + } + + refrel = heap_open(con->refrelid, RowShareLock); + + validateForeignKeyConstraint(fkconstraint, rel, refrel); + + heap_close(refrel, NoLock); + } + } + + if (rel) + heap_close(rel, NoLock); + } +} + +/* + * ATRewriteTable: scan or rewrite one table + * + * OIDNewHeap is InvalidOid if we don't need to rewrite + */ +static void +ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap) +{ + Relation oldrel; + Relation newrel; + TupleDesc oldTupDesc; + TupleDesc newTupDesc; + bool needscan = false; int i; - int minattnum, - maxatts; - HeapTuple typeTuple; - Form_pg_type tform; - int attndims; - ObjectAddress myself, - referenced; + List *l; + EState *estate; /* - * Grab an exclusive lock on the target table, which we will NOT - * release until end of transaction. + * Open the relation(s). We have surely already locked the existing + * table. */ - rel = heap_open(myrelid, AccessExclusiveLock); + oldrel = heap_open(tab->relid, NoLock); + oldTupDesc = tab->oldDesc; + newTupDesc = RelationGetDescr(oldrel); /* includes all mods */ + if (OidIsValid(OIDNewHeap)) + newrel = heap_open(OIDNewHeap, AccessExclusiveLock); + else + newrel = NULL; + + /* + * Generate the constraint and default execution states + */ + + estate = CreateExecutorState(); + + /* Build the needed expression execution states */ + foreach(l, tab->constraints) + { + NewConstraint *con = lfirst(l); + + switch (con->contype) + { + case CONSTR_CHECK: + needscan = true; + con->qualstate = (List *) + ExecPrepareExpr((Expr *) con->qual, estate); + break; + case CONSTR_FOREIGN: + /* Nothing to do here */ + break; + case CONSTR_NOTNULL: + needscan = true; + break; + default: + elog(ERROR, "unrecognized constraint type: %d", + (int) con->contype); + } + } + + foreach(l, tab->newvals) + { + NewColumnValue *ex = lfirst(l); + + needscan = true; + + ex->exprstate = ExecPrepareExpr((Expr *) ex->expr, estate); + } + + if (needscan) + { + ExprContext *econtext; + Datum *values; + char *nulls; + TupleTableSlot *oldslot; + TupleTableSlot *newslot; + HeapScanDesc scan; + HeapTuple tuple; + + econtext = GetPerTupleExprContext(estate); + + /* + * Make tuple slots for old and new tuples. Note that even when + * the tuples are the same, the tupDescs might not be (consider + * ADD COLUMN without a default). + */ + oldslot = MakeTupleTableSlot(); + ExecSetSlotDescriptor(oldslot, oldTupDesc, false); + newslot = MakeTupleTableSlot(); + ExecSetSlotDescriptor(newslot, newTupDesc, false); + + /* Preallocate values/nulls arrays (+1 in case natts==0) */ + i = Max(newTupDesc->natts, oldTupDesc->natts); + values = (Datum *) palloc(i * sizeof(Datum) + 1); + nulls = (char *) palloc(i * sizeof(char) + 1); + memset(values, 0, i * sizeof(Datum)); + memset(nulls, 'n', i * sizeof(char)); + + /* + * Scan through the rows, generating a new row if needed and then + * checking all the constraints. + */ + scan = heap_beginscan(oldrel, SnapshotNow, 0, NULL); + + while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + { + if (newrel) + { + /* + * Extract data from old tuple. We can force to null any + * columns that are deleted according to the new tuple. + */ + int natts = oldTupDesc->natts; + bool isNull; + + for (i = 0; i < natts; i++) + { + if (newTupDesc->attrs[i]->attisdropped) + nulls[i] = 'n'; + else + { + values[i] = heap_getattr(tuple, + i + 1, + oldTupDesc, + &isNull); + if (isNull) + nulls[i] = 'n'; + else + nulls[i] = ' '; + } + } + + /* + * Process supplied expressions to replace selected columns. + * Expression inputs come from the old tuple. + */ + ExecStoreTuple(tuple, oldslot, InvalidBuffer, false); + econtext->ecxt_scantuple = oldslot; + + foreach(l, tab->newvals) + { + NewColumnValue *ex = lfirst(l); + + values[ex->attnum - 1] = ExecEvalExpr(ex->exprstate, + econtext, + &isNull, + NULL); + if (isNull) + nulls[ex->attnum - 1] = 'n'; + else + nulls[ex->attnum - 1] = ' '; + } + + tuple = heap_formtuple(newTupDesc, values, nulls); + } + + /* Now check any constraints on the possibly-changed tuple */ + ExecStoreTuple(tuple, newslot, InvalidBuffer, false); + econtext->ecxt_scantuple = newslot; + + foreach(l, tab->constraints) + { + NewConstraint *con = lfirst(l); + + switch (con->contype) + { + case CONSTR_CHECK: + if (!ExecQual(con->qualstate, econtext, true)) + ereport(ERROR, + (errcode(ERRCODE_CHECK_VIOLATION), + errmsg("check constraint \"%s\" is violated by some row", + con->name))); + break; + case CONSTR_NOTNULL: + { + Datum d; + bool isnull; + + d = heap_getattr(tuple, con->attnum, newTupDesc, + &isnull); + if (isnull) + ereport(ERROR, + (errcode(ERRCODE_NOT_NULL_VIOLATION), + errmsg("column \"%s\" contains null values", + get_attname(tab->relid, + con->attnum)))); + } + break; + case CONSTR_FOREIGN: + /* Nothing to do here */ + break; + default: + elog(ERROR, "unrecognized constraint type: %d", + (int) con->contype); + } + } + + /* Write the tuple out to the new relation */ + if (newrel) + { + simple_heap_insert(newrel, tuple); + + heap_freetuple(tuple); + } + + ResetExprContext(econtext); + + CHECK_FOR_INTERRUPTS(); + } + + heap_endscan(scan); + } + + FreeExecutorState(estate); + + heap_close(oldrel, NoLock); + if (newrel) + heap_close(newrel, NoLock); +} + +/* + * ATGetQueueEntry: find or create an entry in the ALTER TABLE work queue + */ +static AlteredTableInfo * +ATGetQueueEntry(List **wqueue, Relation rel) +{ + Oid relid = RelationGetRelid(rel); + AlteredTableInfo *tab; + List *ltab; + + foreach(ltab, *wqueue) + { + tab = (AlteredTableInfo *) lfirst(ltab); + if (tab->relid == relid) + return tab; + } + + /* + * Not there, so add it. Note that we make a copy of the relation's + * existing descriptor before anything interesting can happen to it. + */ + tab = (AlteredTableInfo *) palloc0(sizeof(AlteredTableInfo)); + tab->relid = relid; + tab->oldDesc = CreateTupleDescCopy(RelationGetDescr(rel)); + + *wqueue = lappend(*wqueue, tab); + + return tab; +} + +/* + * ATSimplePermissions + * + * - Ensure that it is a relation (or possibly a view) + * - Ensure this user is the owner + * - Ensure that it is not a system table + */ +static void +ATSimplePermissions(Relation rel, bool allowView) +{ if (rel->rd_rel->relkind != RELKIND_RELATION) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is not a table", - RelationGetRelationName(rel)))); + { + if (allowView) + { + if (rel->rd_rel->relkind != RELKIND_VIEW) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a table or view", + RelationGetRelationName(rel)))); + } + else + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a table", + RelationGetRelationName(rel)))); + } - /* - * permissions checking. this would normally be done in utility.c, - * but this particular routine is recursive. - * - * normally, only the owner of a class can change its schema. - */ - if (!pg_class_ownercheck(myrelid, GetUserId())) + /* Permissions checks */ + if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(rel)); @@ -1616,87 +2488,111 @@ AlterTableAddColumn(Oid myrelid, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied: \"%s\" is a system catalog", RelationGetRelationName(rel)))); +} +/* + * ATSimpleRecursion + * + * Simple table recursion sufficient for most ALTER TABLE operations. + * All direct and indirect children are processed in an unspecified order. + * Note that if a child inherits from the original table via multiple + * inheritance paths, it will be visited just once. + */ +static void +ATSimpleRecursion(List **wqueue, Relation rel, + AlterTableCmd *cmd, bool recurse) +{ + /* + * Propagate to children if desired. Non-table relations never have + * children, so no need to search in that case. + */ + if (recurse && rel->rd_rel->relkind == RELKIND_RELATION) + { + Oid relid = RelationGetRelid(rel); + List *child, + *children; + + /* this routine is actually in the planner */ + children = find_all_inheritors(relid); + + /* + * find_all_inheritors does the recursive search of the + * inheritance hierarchy, so all we have to do is process all of + * the relids in the list that it returns. + */ + foreach(child, children) + { + Oid childrelid = lfirsto(child); + Relation childrel; + + if (childrelid == relid) + continue; + childrel = relation_open(childrelid, AccessExclusiveLock); + ATPrepCmd(wqueue, childrel, cmd, false, true); + relation_close(childrel, NoLock); + } + } +} + +/* + * ATOneLevelRecursion + * + * Here, we visit only direct inheritance children. It is expected that + * the command's prep routine will recurse again to find indirect children. + * When using this technique, a multiply-inheriting child will be visited + * multiple times. + */ +static void +ATOneLevelRecursion(List **wqueue, Relation rel, + AlterTableCmd *cmd) +{ + Oid relid = RelationGetRelid(rel); + List *child, + *children; + + /* this routine is actually in the planner */ + children = find_inheritance_children(relid); + + foreach(child, children) + { + Oid childrelid = lfirsto(child); + Relation childrel; + + childrel = relation_open(childrelid, AccessExclusiveLock); + ATPrepCmd(wqueue, childrel, cmd, true, true); + relation_close(childrel, NoLock); + } +} + +/* + * ALTER TABLE ADD COLUMN + * + * Adds an additional attribute to a relation making the assumption that + * CHECK, NOT NULL, and FOREIGN KEY constraints will be removed from the + * AT_AddColumn AlterTableCmd by analyze.c and added as independent + * AlterTableCmd's. + */ +static void +ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, + AlterTableCmd *cmd) +{ /* * 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. + * We must recurse one level at a time, so that multiply-inheriting + * children are visited the right number of times and end up with the + * right attinhcount. */ if (recurse) { - List *child, - *children; - ColumnDef *colDefChild = copyObject(colDef); + AlterTableCmd *childCmd = copyObject(cmd); + ColumnDef *colDefChild = (ColumnDef *) childCmd->def; /* Child should see column as singly inherited */ colDefChild->inhcount = 1; colDefChild->is_local = false; - /* We only want direct inheritors */ - children = find_inheritance_children(myrelid); - - foreach(child, children) - { - Oid childrelid = lfirsto(child); - HeapTuple tuple; - Form_pg_attribute childatt; - Relation childrel; - - if (childrelid == myrelid) - continue; - - childrel = heap_open(childrelid, AccessExclusiveLock); - - /* Does child already have a column by this name? */ - attrdesc = heap_openr(AttributeRelationName, RowExclusiveLock); - tuple = SearchSysCacheCopyAttName(childrelid, colDef->colname); - if (!HeapTupleIsValid(tuple)) - { - /* No, recurse to add it normally */ - heap_close(attrdesc, RowExclusiveLock); - heap_close(childrel, NoLock); - AlterTableAddColumn(childrelid, true, colDefChild); - continue; - } - childatt = (Form_pg_attribute) GETSTRUCT(tuple); - - /* Okay if child matches by type */ - if (typenameTypeId(colDef->typename) != childatt->atttypid || - colDef->typename->typmod != childatt->atttypmod) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("child table \"%s\" has different type for column \"%s\"", - get_rel_name(childrelid), colDef->colname))); - - /* - * XXX if we supported NOT NULL or defaults, would need to do - * more work here to verify child matches - */ - ereport(NOTICE, - (errmsg("merging definition of column \"%s\" for child \"%s\"", - colDef->colname, get_rel_name(childrelid)))); - - /* Bump the existing child att's inhcount */ - childatt->attinhcount++; - simple_heap_update(attrdesc, &tuple->t_self, tuple); - CatalogUpdateIndexes(attrdesc, tuple); - - /* - * Propagate any new CHECK constraints into the child table - * and its descendants - */ - if (colDef->constraints != NIL) - { - CommandCounterIncrement(); - AlterTableAddConstraint(childrelid, true, colDef->constraints); - } - - heap_freetuple(tuple); - heap_close(attrdesc, RowExclusiveLock); - heap_close(childrel, NoLock); - } + ATOneLevelRecursion(wqueue, rel, childCmd); } else { @@ -1704,42 +2600,77 @@ AlterTableAddColumn(Oid myrelid, * If we are told not to recurse, there had better not be any * child tables; else the addition would put them out of step. */ - if (find_inheritance_children(myrelid) != NIL) + if (find_inheritance_children(RelationGetRelid(rel)) != NIL) ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("column must be added to child tables too"))); } +} + +static void +ATExecAddColumn(AlteredTableInfo *tab, Relation rel, + ColumnDef *colDef) +{ + Oid myrelid = RelationGetRelid(rel); + Relation pgclass, + attrdesc; + HeapTuple reltup; + HeapTuple attributeTuple; + Form_pg_attribute attribute; + FormData_pg_attribute attributeD; + int i; + int minattnum, + maxatts; + HeapTuple typeTuple; + Form_pg_type tform; + Expr *defval; + + attrdesc = heap_openr(AttributeRelationName, RowExclusiveLock); /* - * 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)). + * Are we adding the column to a recursion child? If so, check whether + * to merge with an existing definition for the column. */ - if (colDef->raw_default || colDef->cooked_default) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("adding columns with defaults is not implemented"), - errhint("Add the column, then use ALTER TABLE SET DEFAULT."))); + if (colDef->inhcount > 0) + { + HeapTuple tuple; - if (colDef->is_not_null) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("adding NOT NULL columns is not implemented"), - errhint("Add the column, then use ALTER TABLE SET NOT NULL."))); + /* Does child already have a column by this name? */ + tuple = SearchSysCacheCopyAttName(myrelid, colDef->colname); + if (HeapTupleIsValid(tuple)) + { + Form_pg_attribute childatt = (Form_pg_attribute) GETSTRUCT(tuple); + + /* Okay if child matches by type */ + if (typenameTypeId(colDef->typename) != childatt->atttypid || + colDef->typename->typmod != childatt->atttypmod) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("child table \"%s\" has different type for column \"%s\"", + RelationGetRelationName(rel), colDef->colname))); + + /* Bump the existing child att's inhcount */ + childatt->attinhcount++; + simple_heap_update(attrdesc, &tuple->t_self, tuple); + CatalogUpdateIndexes(attrdesc, tuple); + + heap_freetuple(tuple); + + /* Inform the user about the merge */ + ereport(NOTICE, + (errmsg("merging definition of column \"%s\" for child \"%s\"", + colDef->colname, RelationGetRelationName(rel)))); + + heap_close(attrdesc, RowExclusiveLock); + return; + } + } pgclass = heap_openr(RelationRelationName, RowExclusiveLock); - reltup = SearchSysCache(RELOID, - ObjectIdGetDatum(myrelid), - 0, 0, 0); + reltup = SearchSysCacheCopy(RELOID, + ObjectIdGetDatum(myrelid), + 0, 0, 0); if (!HeapTupleIsValid(reltup)) elog(ERROR, "cache lookup failed for relation %u", myrelid); @@ -1766,13 +2697,6 @@ AlterTableAddColumn(Oid myrelid, 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); @@ -1795,12 +2719,11 @@ AlterTableAddColumn(Oid myrelid, attribute->atttypmod = colDef->typename->typmod; attribute->attnum = i; attribute->attbyval = tform->typbyval; - attribute->attndims = attndims; + attribute->attndims = length(colDef->typename->arrayBounds); attribute->attstorage = tform->typstorage; attribute->attalign = tform->typalign; attribute->attnotnull = colDef->is_not_null; - attribute->atthasdef = (colDef->raw_default != NULL || - colDef->cooked_default != NULL); + attribute->atthasdef = false; attribute->attisdropped = false; attribute->attislocal = colDef->is_local; attribute->attinhcount = colDef->inhcount; @@ -1817,132 +2740,120 @@ AlterTableAddColumn(Oid myrelid, /* * Update number of attributes in pg_class tuple */ - newreltup = heap_copytuple(reltup); + ((Form_pg_class) GETSTRUCT(reltup))->relnatts = maxatts; - ((Form_pg_class) GETSTRUCT(newreltup))->relnatts = maxatts; - - simple_heap_update(pgclass, &newreltup->t_self, newreltup); + simple_heap_update(pgclass, &reltup->t_self, reltup); /* keep catalog indexes current */ - CatalogUpdateIndexes(pgclass, newreltup); + CatalogUpdateIndexes(pgclass, reltup); - heap_freetuple(newreltup); - ReleaseSysCache(reltup); + heap_freetuple(reltup); heap_close(pgclass, RowExclusiveLock); - heap_close(rel, NoLock); /* close rel but keep lock! */ + /* Make the attribute's catalog entry visible */ + CommandCounterIncrement(); + + /* + * Store the DEFAULT, if any, in the catalogs + */ + if (colDef->raw_default) + { + RawColumnDefault *rawEnt; + + rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault)); + rawEnt->attnum = attribute->attnum; + rawEnt->raw_default = copyObject(colDef->raw_default); + + /* + * This function is intended for CREATE TABLE, so it processes a + * _list_ of defaults, but we just do one. + */ + AddRelationRawConstraints(rel, makeList1(rawEnt), NIL); + + /* Make the additional catalog changes visible */ + CommandCounterIncrement(); + } + + /* + * Tell Phase 3 to fill in the default expression, if there is one. + * + * If there is no default, Phase 3 doesn't have to do anything, + * because that effectively means that the default is NULL. The + * heap tuple access routines always check for attnum > # of attributes + * in tuple, and return NULL if so, so without any modification of + * the tuple data we will get the effect of NULL values in the new + * column. + * + * Note: we use build_column_default, and not just the cooked default + * returned by AddRelationRawConstraints, so that the right thing happens + * when a datatype's default applies. + */ + defval = (Expr *) build_column_default(rel, attribute->attnum); + if (defval) + { + NewColumnValue *newval; + + newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue)); + newval->attnum = attribute->attnum; + newval->expr = defval; + + tab->newvals = lappend(tab->newvals, newval); + } /* * Add datatype dependency for the new column. */ + add_column_datatype_dependency(myrelid, i, attribute->atttypid); +} + +/* + * Install a column's dependency on its datatype. + */ +static void +add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid) +{ + ObjectAddress myself, + referenced; + myself.classId = RelOid_pg_class; - myself.objectId = myrelid; - myself.objectSubId = i; + myself.objectId = relid; + myself.objectSubId = attnum; referenced.classId = RelOid_pg_type; - referenced.objectId = attribute->atttypid; + referenced.objectId = typid; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); - - /* - * Make our catalog updates visible for subsequent steps. - */ - CommandCounterIncrement(); - - /* - * Add any CHECK constraints attached to the new column. - * - * To do this we must re-open the rel so that its new attr list gets - * loaded into the relcache. - */ - if (colDef->constraints != NIL) - { - rel = heap_open(myrelid, AccessExclusiveLock); - AddRelationRawConstraints(rel, NIL, colDef->constraints); - heap_close(rel, NoLock); - } - - /* - * Automatically create the secondary relation for TOAST if it - * formerly had no such but now has toastable attributes. - */ - AlterTableCreateToastTable(myrelid, true); } /* * ALTER TABLE ALTER COLUMN DROP NOT NULL */ -void -AlterTableAlterColumnDropNotNull(Oid myrelid, bool recurse, - const char *colName) +static void +ATExecDropNotNull(Relation rel, const char *colName) { - Relation rel; HeapTuple tuple; AttrNumber attnum; Relation attr_rel; List *indexoidlist; List *indexoidscan; - rel = heap_open(myrelid, AccessExclusiveLock); - - if (rel->rd_rel->relkind != RELKIND_RELATION) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is not a table", - RelationGetRelationName(rel)))); - - /* Permissions checks */ - if (!pg_class_ownercheck(myrelid, GetUserId())) - aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, - RelationGetRelationName(rel)); - - if (!allowSystemTableMods && IsSystemRelation(rel)) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("permission denied: \"%s\" is a system catalog", - RelationGetRelationName(rel)))); - /* - * Propagate to children if desired + * lookup the attribute */ - if (recurse) - { - List *child, - *children; + attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock); - /* this routine is actually in the planner */ - children = find_all_inheritors(myrelid); + tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); - /* - * 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 = lfirsto(child); - - if (childrelid == myrelid) - continue; - AlterTableAlterColumnDropNotNull(childrelid, - false, colName); - } - } - - /* now do the thing on this relation */ - - /* - * get the number of the attribute - */ - attnum = get_attnum(myrelid, colName); - if (attnum == InvalidAttrNumber) + if (!HeapTupleIsValid(tuple)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("column \"%s\" of relation \"%s\" does not exist", - colName, RelationGetRelationName(rel)))); + errmsg("column \"%s\" of relation \"%s\" does not exist", + colName, RelationGetRelationName(rel)))); + + attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum; /* Prevent them from altering a system attribute */ - if (attnum < 0) + if (attnum <= 0) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot alter system column \"%s\"", @@ -1992,221 +2903,92 @@ AlterTableAlterColumnDropNotNull(Oid myrelid, bool recurse, freeList(indexoidlist); /* - * Okay, actually perform the catalog change + * Okay, actually perform the catalog change ... if needed */ - attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock); + if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull) + { + ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = FALSE; - tuple = SearchSysCacheCopyAttName(myrelid, colName); - if (!HeapTupleIsValid(tuple)) /* shouldn't happen */ - elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u", - colName, myrelid); + simple_heap_update(attr_rel, &tuple->t_self, tuple); - ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = FALSE; - - simple_heap_update(attr_rel, &tuple->t_self, tuple); - - /* keep the system catalog indexes current */ - CatalogUpdateIndexes(attr_rel, tuple); + /* keep the system catalog indexes current */ + CatalogUpdateIndexes(attr_rel, tuple); + } heap_close(attr_rel, RowExclusiveLock); - - heap_close(rel, NoLock); } /* * ALTER TABLE ALTER COLUMN SET NOT NULL */ -void -AlterTableAlterColumnSetNotNull(Oid myrelid, bool recurse, - const char *colName) +static void +ATExecSetNotNull(AlteredTableInfo *tab, Relation rel, + const char *colName) { - Relation rel; HeapTuple tuple; AttrNumber attnum; Relation attr_rel; - HeapScanDesc scan; - TupleDesc tupdesc; - - rel = heap_open(myrelid, AccessExclusiveLock); - - if (rel->rd_rel->relkind != RELKIND_RELATION) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is not a table", - RelationGetRelationName(rel)))); - - /* Permissions checks */ - if (!pg_class_ownercheck(myrelid, GetUserId())) - aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, - RelationGetRelationName(rel)); - - if (!allowSystemTableMods && IsSystemRelation(rel)) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("permission denied: \"%s\" is a system catalog", - RelationGetRelationName(rel)))); + NewConstraint *newcon; /* - * Propagate to children if desired + * lookup the attribute */ - if (recurse) - { - List *child, - *children; + attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock); - /* this routine is actually in the planner */ - children = find_all_inheritors(myrelid); + tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); - /* - * 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 = lfirsto(child); - - if (childrelid == myrelid) - continue; - AlterTableAlterColumnSetNotNull(childrelid, - false, colName); - } - } - - /* now do the thing on this relation */ - - /* - * get the number of the attribute - */ - attnum = get_attnum(myrelid, colName); - if (attnum == InvalidAttrNumber) + if (!HeapTupleIsValid(tuple)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("column \"%s\" of relation \"%s\" does not exist", - colName, RelationGetRelationName(rel)))); + errmsg("column \"%s\" of relation \"%s\" does not exist", + colName, RelationGetRelationName(rel)))); + + attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum; /* Prevent them from altering a system attribute */ - if (attnum < 0) + if (attnum <= 0) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot alter system column \"%s\"", colName))); /* - * Perform a scan to ensure that there are no NULL values already in - * the relation + * Okay, actually perform the catalog change ... if needed */ - tupdesc = RelationGetDescr(rel); - - scan = heap_beginscan(rel, SnapshotNow, 0, NULL); - - while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + if (! ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull) { - Datum d; - bool isnull; + ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = TRUE; - d = heap_getattr(tuple, attnum, tupdesc, &isnull); + simple_heap_update(attr_rel, &tuple->t_self, tuple); - if (isnull) - ereport(ERROR, - (errcode(ERRCODE_NOT_NULL_VIOLATION), - errmsg("column \"%s\" contains null values", - colName))); + /* keep the system catalog indexes current */ + CatalogUpdateIndexes(attr_rel, tuple); + + /* Tell Phase 3 to test the constraint */ + newcon = (NewConstraint *) palloc0(sizeof(NewConstraint)); + newcon->contype = CONSTR_NOTNULL; + newcon->attnum = attnum; + newcon->name = "NOT NULL"; + + tab->constraints = lappend(tab->constraints, newcon); } - heap_endscan(scan); - - /* - * Okay, actually perform the catalog change - */ - attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock); - - tuple = SearchSysCacheCopyAttName(myrelid, colName); - if (!HeapTupleIsValid(tuple)) /* shouldn't happen */ - elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u", - colName, myrelid); - - ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = TRUE; - - simple_heap_update(attr_rel, &tuple->t_self, tuple); - - /* keep the system catalog indexes current */ - CatalogUpdateIndexes(attr_rel, tuple); - heap_close(attr_rel, RowExclusiveLock); - - heap_close(rel, NoLock); } /* * ALTER TABLE ALTER COLUMN SET/DROP DEFAULT */ -void -AlterTableAlterColumnDefault(Oid myrelid, bool recurse, - const char *colName, - Node *newDefault) +static void +ATExecColumnDefault(Relation rel, const char *colName, + Node *newDefault) { - Relation rel; AttrNumber attnum; - rel = heap_open(myrelid, AccessExclusiveLock); - - /* - * We allow defaults on views so that INSERT into a view can have - * default-ish behavior. This works because the rewriter substitutes - * default values into INSERTs before it expands rules. - */ - if (rel->rd_rel->relkind != RELKIND_RELATION && - rel->rd_rel->relkind != RELKIND_VIEW) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is not a table or view", - RelationGetRelationName(rel)))); - - /* Permissions checks */ - if (!pg_class_ownercheck(myrelid, GetUserId())) - aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, - RelationGetRelationName(rel)); - - if (!allowSystemTableMods && IsSystemRelation(rel)) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("permission denied: \"%s\" is a system catalog", - RelationGetRelationName(rel)))); - - /* - * Propagate to children if desired - */ - if (recurse) - { - 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 = lfirsto(child); - - if (childrelid == myrelid) - continue; - AlterTableAlterColumnDefault(childrelid, - false, colName, newDefault); - } - } - - /* now do the thing on this relation */ - /* * get the number of the attribute */ - attnum = get_attnum(myrelid, colName); + attnum = get_attnum(RelationGetRelid(rel), colName); if (attnum == InvalidAttrNumber) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), @@ -2214,7 +2996,7 @@ AlterTableAlterColumnDefault(Oid myrelid, bool recurse, colName, RelationGetRelationName(rel)))); /* Prevent them from altering a system attribute */ - if (attnum < 0) + if (attnum <= 0) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot alter system column \"%s\"", @@ -2225,7 +3007,7 @@ AlterTableAlterColumnDefault(Oid myrelid, bool recurse, * safety, but at present we do not expect anything to depend on the * default. */ - RemoveAttrDefault(myrelid, attnum, DROP_RESTRICT, false); + RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false); if (newDefault) { @@ -2242,141 +3024,67 @@ AlterTableAlterColumnDefault(Oid myrelid, bool recurse, */ AddRelationRawConstraints(rel, makeList1(rawEnt), NIL); } - - heap_close(rel, NoLock); } /* - * ALTER TABLE ALTER COLUMN SET STATISTICS / STORAGE + * ALTER TABLE ALTER COLUMN SET STATISTICS */ -void -AlterTableAlterColumnFlags(Oid myrelid, bool recurse, - const char *colName, - Node *flagValue, const char *flagType) +static void +ATPrepSetStatistics(Relation rel, const char *colName, Node *flagValue) { - Relation rel; - int newtarget = 1; - char newstorage = 'p'; + /* + * We do our own permission checking because (a) we want to allow + * SET STATISTICS on indexes (for expressional index columns), and + * (b) we want to allow SET STATISTICS on system catalogs without + * requiring allowSystemTableMods to be turned on. + */ + if (rel->rd_rel->relkind != RELKIND_RELATION && + rel->rd_rel->relkind != RELKIND_INDEX) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a table or index", + RelationGetRelationName(rel)))); + + /* Permissions checks */ + if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, + RelationGetRelationName(rel)); +} + +static void +ATExecSetStatistics(Relation rel, const char *colName, Node *newValue) +{ + int newtarget; Relation attrelation; HeapTuple tuple; Form_pg_attribute attrtuple; - rel = relation_open(myrelid, AccessExclusiveLock); + Assert(IsA(newValue, Integer)); + newtarget = intVal(newValue); /* - * Allow index for statistics case only + * Limit target to a sane range */ - if (rel->rd_rel->relkind != RELKIND_RELATION) + if (newtarget < -1) { - if (rel->rd_rel->relkind != RELKIND_INDEX || *flagType != 'S') - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is not a table", - RelationGetRelationName(rel)))); - } - - /* Permissions checks */ - if (!pg_class_ownercheck(myrelid, GetUserId())) - aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, - RelationGetRelationName(rel)); - - /* - * we allow statistics case for system tables - */ - if (*flagType != 'S' && !allowSystemTableMods && IsSystemRelation(rel)) ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("permission denied: \"%s\" is a system catalog", - RelationGetRelationName(rel)))); - - /* - * Check the supplied parameters before anything else - */ - if (*flagType == 'S') - { - /* STATISTICS */ - Assert(IsA(flagValue, Integer)); - newtarget = intVal(flagValue); - - /* - * Limit target to a sane range - */ - if (newtarget < -1) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("statistics target %d is too low", - newtarget))); - } - else if (newtarget > 1000) - { - newtarget = 1000; - ereport(WARNING, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("lowering statistics target to %d", - newtarget))); - } + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("statistics target %d is too low", + newtarget))); } - else if (*flagType == 'M') + else if (newtarget > 1000) { - /* STORAGE */ - char *storagemode; - - Assert(IsA(flagValue, String)); - storagemode = strVal(flagValue); - - if (strcasecmp(storagemode, "plain") == 0) - newstorage = 'p'; - else if (strcasecmp(storagemode, "external") == 0) - newstorage = 'e'; - else if (strcasecmp(storagemode, "extended") == 0) - newstorage = 'x'; - else if (strcasecmp(storagemode, "main") == 0) - newstorage = 'm'; - else - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid storage type \"%s\"", - storagemode))); + newtarget = 1000; + ereport(WARNING, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("lowering statistics target to %d", + newtarget))); } - else - { - elog(ERROR, "unrecognized alter-column type flag: %c", - (int) *flagType); - } - - /* - * Propagate to children if desired - */ - if (recurse && rel->rd_rel->relkind == RELKIND_RELATION) - { - 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 = lfirsto(child); - - if (childrelid == myrelid) - continue; - AlterTableAlterColumnFlags(childrelid, - false, colName, flagValue, flagType); - } - } - - /* now do the thing on this relation */ attrelation = heap_openr(AttributeRelationName, RowExclusiveLock); - tuple = SearchSysCacheCopyAttName(myrelid, colName); + tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); + if (!HeapTupleIsValid(tuple)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), @@ -2384,31 +3092,13 @@ AlterTableAlterColumnFlags(Oid myrelid, bool recurse, colName, RelationGetRelationName(rel)))); attrtuple = (Form_pg_attribute) GETSTRUCT(tuple); - if (attrtuple->attnum < 0) + if (attrtuple->attnum <= 0) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot alter system column \"%s\"", colName))); - /* - * Now change the appropriate field - */ - if (*flagType == 'S') - attrtuple->attstattarget = newtarget; - else if (*flagType == 'M') - { - /* - * safety check: do not allow toasted storage modes unless column - * datatype is TOAST-aware. - */ - if (newstorage == 'p' || TypeIsToastable(attrtuple->atttypid)) - attrtuple->attstorage = newstorage; - else - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("column data type %s can only have storage PLAIN", - format_type_be(attrtuple->atttypid)))); - } + attrtuple->attstattarget = newtarget; simple_heap_update(attrelation, &tuple->t_self, tuple); @@ -2418,85 +3108,110 @@ AlterTableAlterColumnFlags(Oid myrelid, bool recurse, heap_freetuple(tuple); heap_close(attrelation, RowExclusiveLock); - - heap_close(rel, NoLock); /* close rel, but keep lock! */ } /* - * ALTER TABLE SET WITH/WITHOUT OIDS + * ALTER TABLE ALTER COLUMN SET STORAGE */ -void -AlterTableAlterOids(Oid myrelid, bool setOid, bool recurse, - DropBehavior behavior) +static void +ATExecSetStorage(Relation rel, const char *colName, Node *newValue) { - Relation rel; + char *storagemode; + char newstorage; + Relation attrelation; + HeapTuple tuple; + Form_pg_attribute attrtuple; - rel = heap_open(myrelid, AccessExclusiveLock); + Assert(IsA(newValue, String)); + storagemode = strVal(newValue); - /* - * check to see if we actually need to change anything - */ - if (rel->rd_rel->relhasoids == setOid) - { - heap_close(rel, NoLock); /* close rel, but keep lock! */ - return; - } - - if (setOid) - { - /* - * TODO: Generate the now required OID pg_attribute entry, and - * modify physical rows to have OIDs. - */ - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("ALTER TABLE WITH OIDS is not yet implemented"))); - } + 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 { - heap_close(rel, NoLock); /* close rel, but keep lock! */ - - AlterTableDropColumn(myrelid, recurse, false, "oid", behavior); + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid storage type \"%s\"", + storagemode))); + newstorage = 0; /* keep compiler quiet */ } + + attrelation = heap_openr(AttributeRelationName, RowExclusiveLock); + + tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); + + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + colName, RelationGetRelationName(rel)))); + attrtuple = (Form_pg_attribute) GETSTRUCT(tuple); + + if (attrtuple->attnum <= 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot alter system column \"%s\"", + colName))); + + /* + * safety check: do not allow toasted storage modes unless column + * datatype is TOAST-aware. + */ + if (newstorage == 'p' || TypeIsToastable(attrtuple->atttypid)) + attrtuple->attstorage = newstorage; + else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("column data type %s can only have storage PLAIN", + format_type_be(attrtuple->atttypid)))); + + simple_heap_update(attrelation, &tuple->t_self, tuple); + + /* keep system catalog indexes current */ + CatalogUpdateIndexes(attrelation, tuple); + + heap_freetuple(tuple); + + heap_close(attrelation, RowExclusiveLock); } + /* * ALTER TABLE DROP COLUMN + * + * DROP COLUMN cannot use the normal ALTER TABLE recursion mechanism, + * because we have to decide at runtime whether to recurse or not depending + * on whether attinhcount goes to zero or not. (We can't check this in a + * static pre-pass because it won't handle multiple inheritance situations + * correctly.) Since DROP COLUMN doesn't need to create any work queue + * entries for Phase 3, it's okay to recurse internally in this routine + * without considering the work queue. */ -void -AlterTableDropColumn(Oid myrelid, bool recurse, bool recursing, - const char *colName, - DropBehavior behavior) +static void +ATExecDropColumn(Relation rel, const char *colName, + DropBehavior behavior, + bool recurse, bool recursing) { - Relation rel; - AttrNumber attnum; HeapTuple tuple; Form_pg_attribute targetatt; + AttrNumber attnum; + List *children; ObjectAddress object; - rel = heap_open(myrelid, AccessExclusiveLock); - - if (rel->rd_rel->relkind != RELKIND_RELATION) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is not a table", - RelationGetRelationName(rel)))); - - /* Permissions checks */ - if (!pg_class_ownercheck(myrelid, GetUserId())) - aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, - RelationGetRelationName(rel)); - - if (!allowSystemTableMods && IsSystemRelation(rel)) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("permission denied: \"%s\" is a system catalog", - RelationGetRelationName(rel)))); + /* At top level, permission check was done in ATPrepCmd, else do it */ + if (recursing) + ATSimplePermissions(rel, false); /* * get the number of the attribute */ - tuple = SearchSysCacheAttName(myrelid, colName); + tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName); if (!HeapTupleIsValid(tuple)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), @@ -2523,65 +3238,16 @@ AlterTableDropColumn(Oid myrelid, bool recurse, bool recursing, ReleaseSysCache(tuple); /* - * If we are asked to drop ONLY in this table (no recursion), we need - * to mark the inheritors' attribute as locally defined rather than - * inherited. - */ - if (!recurse && !recursing) - { - Relation attr_rel; - List *child, - *children; - - /* We only want direct inheritors in this case */ - children = find_inheritance_children(myrelid); - - attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock); - foreach(child, children) - { - Oid childrelid = lfirsto(child); - Relation childrel; - Form_pg_attribute childatt; - - childrel = heap_open(childrelid, AccessExclusiveLock); - - tuple = SearchSysCacheCopyAttName(childrelid, colName); - if (!HeapTupleIsValid(tuple)) /* shouldn't happen */ - elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u", - colName, childrelid); - childatt = (Form_pg_attribute) GETSTRUCT(tuple); - - if (childatt->attinhcount <= 0) /* shouldn't happen */ - elog(ERROR, "relation %u has non-inherited attribute \"%s\"", - childrelid, colName); - childatt->attinhcount--; - childatt->attislocal = true; - - simple_heap_update(attr_rel, &tuple->t_self, tuple); - - /* keep the system catalog indexes current */ - CatalogUpdateIndexes(attr_rel, tuple); - - heap_freetuple(tuple); - - heap_close(childrel, NoLock); - } - heap_close(attr_rel, RowExclusiveLock); - } - - /* - * Propagate to children if desired. Unlike most other ALTER + * Propagate to children as appropriate. Unlike most other ALTER * routines, we have to do this one level of recursion at a time; we * can't use find_all_inheritors to do it in one pass. */ - if (recurse) + children = find_inheritance_children(RelationGetRelid(rel)); + + if (children) { Relation attr_rel; - List *child, - *children; - - /* We only want direct inheritors in this case */ - children = find_inheritance_children(myrelid); + List *child; attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock); foreach(child, children) @@ -2590,9 +3256,6 @@ AlterTableDropColumn(Oid myrelid, bool recurse, bool recursing, Relation childrel; Form_pg_attribute childatt; - if (childrelid == myrelid) - continue; - childrel = heap_open(childrelid, AccessExclusiveLock); tuple = SearchSysCacheCopyAttName(childrelid, colName); @@ -2605,20 +3268,49 @@ AlterTableDropColumn(Oid myrelid, bool recurse, bool recursing, elog(ERROR, "relation %u has non-inherited attribute \"%s\"", childrelid, colName); - if (childatt->attinhcount == 1 && !childatt->attislocal) + if (recurse) { - /* Time to delete this child column, too */ - AlterTableDropColumn(childrelid, true, true, colName, behavior); + /* + * If the child column has other definition sources, just + * decrement its inheritance count; if not, recurse to delete + * it. + */ + if (childatt->attinhcount == 1 && !childatt->attislocal) + { + /* Time to delete this child column, too */ + ATExecDropColumn(childrel, colName, behavior, true, true); + } + else + { + /* Child column must survive my deletion */ + childatt->attinhcount--; + + simple_heap_update(attr_rel, &tuple->t_self, tuple); + + /* keep the system catalog indexes current */ + CatalogUpdateIndexes(attr_rel, tuple); + + /* Make update visible */ + CommandCounterIncrement(); + } } else { - /* Child column must survive my deletion */ + /* + * If we were told to drop ONLY in this table (no recursion), + * we need to mark the inheritors' attribute as locally + * defined rather than inherited. + */ childatt->attinhcount--; + childatt->attislocal = true; simple_heap_update(attr_rel, &tuple->t_self, tuple); /* keep the system catalog indexes current */ CatalogUpdateIndexes(attr_rel, tuple); + + /* Make update visible */ + CommandCounterIncrement(); } heap_freetuple(tuple); @@ -2632,7 +3324,7 @@ AlterTableDropColumn(Oid myrelid, bool recurse, bool recursing, * Perform the actual column deletion */ object.classId = RelOid_pg_class; - object.objectId = myrelid; + object.objectId = RelationGetRelid(rel); object.objectSubId = attnum; performDeletion(&object, behavior); @@ -2648,10 +3340,11 @@ AlterTableDropColumn(Oid myrelid, bool recurse, bool recursing, class_rel = heap_openr(RelationRelationName, RowExclusiveLock); tuple = SearchSysCacheCopy(RELOID, - ObjectIdGetDatum(myrelid), + ObjectIdGetDatum(RelationGetRelid(rel)), 0, 0, 0); if (!HeapTupleIsValid(tuple)) - elog(ERROR, "cache lookup failed for relation %u", myrelid); + elog(ERROR, "cache lookup failed for relation %u", + RelationGetRelid(rel)); tuple_class = (Form_pg_class) GETSTRUCT(tuple); tuple_class->relhasoids = false; @@ -2662,298 +3355,149 @@ AlterTableDropColumn(Oid myrelid, bool recurse, bool recursing, heap_close(class_rel, RowExclusiveLock); } - - heap_close(rel, NoLock); /* close rel, but keep lock! */ } +/* + * ALTER TABLE ADD INDEX + * + * There is no such command in the grammar, but the parser converts UNIQUE + * and PRIMARY KEY constraints into AT_AddIndex subcommands. This lets us + * schedule creation of the index at the appropriate time during ALTER. + */ +static void +ATExecAddIndex(AlteredTableInfo *tab, Relation rel, + IndexStmt *stmt, bool is_rebuild) +{ + bool check_rights; + bool skip_build; + bool quiet; + + Assert(IsA(stmt, IndexStmt)); + + /* suppress schema rights check when rebuilding existing index */ + check_rights = !is_rebuild; + /* skip index build if phase 3 will have to rewrite table anyway */ + skip_build = (tab->newvals != NIL); + /* suppress notices when rebuilding existing index */ + quiet = is_rebuild; + + DefineIndex(stmt->relation, /* relation */ + stmt->idxname, /* index name */ + stmt->accessMethod, /* am name */ + stmt->indexParams, /* parameters */ + (Expr *) stmt->whereClause, + stmt->rangetable, + stmt->unique, + stmt->primary, + stmt->isconstraint, + true, /* is_alter_table */ + check_rights, + skip_build, + quiet); +} /* * ALTER TABLE ADD CONSTRAINT */ -void -AlterTableAddConstraint(Oid myrelid, bool recurse, - List *newConstraints) -{ - Relation rel; - List *listptr; - int counter = 0; - - /* - * 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) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is not a table", - RelationGetRelationName(rel)))); - - /* Permissions checks */ - if (!pg_class_ownercheck(myrelid, GetUserId())) - aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, - RelationGetRelationName(rel)); - - if (!allowSystemTableMods && IsSystemRelation(rel)) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("permission denied: \"%s\" is a system catalog", - RelationGetRelationName(rel)))); - - if (recurse) - { - 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 = lfirsto(child); - - if (childrelid == myrelid) - continue; - AlterTableAddConstraint(childrelid, false, newConstraints); - } - } - - foreach(listptr, newConstraints) - { - /* - * copy is because we may destructively alter the node below by - * inserting a generated name; this name is not necessarily - * correct for children or parents. - */ - Node *newConstraint = copyObject(lfirst(listptr)); - - switch (nodeTag(newConstraint)) - { - case T_Constraint: - { - Constraint *constr = (Constraint *) newConstraint; - - /* - * Assign or validate constraint name - */ - if (constr->name) - { - if (ConstraintNameIsUsed(CONSTRAINT_RELATION, - RelationGetRelid(rel), - RelationGetNamespace(rel), - constr->name)) - ereport(ERROR, - (errcode(ERRCODE_DUPLICATE_OBJECT), - errmsg("constraint \"%s\" for relation \"%s\" already exists", - constr->name, - RelationGetRelationName(rel)))); - } - else - constr->name = GenerateConstraintName(CONSTRAINT_RELATION, - RelationGetRelid(rel), - RelationGetNamespace(rel), - &counter); - - /* - * 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: - AlterTableAddCheckConstraint(rel, constr); - break; - default: - elog(ERROR, "unrecognized constraint type: %d", - (int) constr->contype); - } - break; - } - case T_FkConstraint: - { - FkConstraint *fkconstraint = (FkConstraint *) newConstraint; - - /* - * Assign or validate constraint name - */ - if (fkconstraint->constr_name) - { - if (ConstraintNameIsUsed(CONSTRAINT_RELATION, - RelationGetRelid(rel), - RelationGetNamespace(rel), - fkconstraint->constr_name)) - ereport(ERROR, - (errcode(ERRCODE_DUPLICATE_OBJECT), - errmsg("constraint \"%s\" for relation \"%s\" already exists", - fkconstraint->constr_name, - RelationGetRelationName(rel)))); - } - else - fkconstraint->constr_name = GenerateConstraintName(CONSTRAINT_RELATION, - RelationGetRelid(rel), - RelationGetNamespace(rel), - &counter); - - AlterTableAddForeignKeyConstraint(rel, fkconstraint); - - break; - } - default: - elog(ERROR, "unrecognized node type: %d", - (int) nodeTag(newConstraint)); - } - - /* If we have multiple constraints to make, bump CC between 'em */ - if (lnext(listptr)) - CommandCounterIncrement(); - } - - /* Close rel, but keep lock till commit */ - heap_close(rel, NoLock); -} - -/* - * Add a check constraint to a single table - * - * Subroutine for AlterTableAddConstraint. Must already hold exclusive - * lock on the rel, and have done appropriate validity/permissions checks - * for it. - */ static void -AlterTableAddCheckConstraint(Relation rel, Constraint *constr) +ATExecAddConstraint(AlteredTableInfo *tab, Relation rel, Node *newConstraint) { - ParseState *pstate; - bool successful = true; - HeapScanDesc scan; - EState *estate; - ExprContext *econtext; - TupleTableSlot *slot; - HeapTuple tuple; - RangeTblEntry *rte; - List *qual; - List *qualstate; - Node *expr; - - /* - * We need to make a parse state and range table to allow us to do - * transformExpr() - */ - pstate = make_parsestate(NULL); - rte = addRangeTableEntryForRelation(pstate, - RelationGetRelid(rel), - 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. - */ - expr = coerce_to_boolean(pstate, expr, "CHECK"); - - /* - * Make sure no outside relations are referred to. - */ - if (length(pstate->p_rtable) != 1) - ereport(ERROR, - (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), - errmsg("check constraint may only reference relation \"%s\"", - RelationGetRelationName(rel)))); - - /* - * No subplans or aggregates, either... - */ - if (pstate->p_hasSubLinks) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot use subquery in check constraint"))); - if (pstate->p_hasAggs) - ereport(ERROR, - (errcode(ERRCODE_GROUPING_ERROR), - errmsg("cannot use aggregate function in check constraint"))); - - /* - * Might as well try to reduce any constant expressions, so as to - * minimize overhead while testing the constraint at each row. - * - * Note that the stored form of the constraint will NOT be const-folded. - */ - expr = eval_const_expressions(expr); - - /* Needs to be in implicit-ANDs form for ExecQual */ - qual = make_ands_implicit((Expr *) expr); - - /* Need an EState to run ExecQual */ - estate = CreateExecutorState(); - econtext = GetPerTupleExprContext(estate); - - /* build execution state for qual */ - qualstate = (List *) ExecPrepareExpr((Expr *) qual, estate); - - /* Make tuple slot to hold tuples */ - slot = MakeTupleTableSlot(); - ExecSetSlotDescriptor(slot, RelationGetDescr(rel), false); - - /* Arrange for econtext's scan tuple to be the tuple under test */ - econtext->ecxt_scantuple = slot; - - /* - * Scan through the rows now, checking the expression at each row. - */ - scan = heap_beginscan(rel, SnapshotNow, 0, NULL); - - while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + switch (nodeTag(newConstraint)) { - ExecStoreTuple(tuple, slot, InvalidBuffer, false); - if (!ExecQual(qualstate, econtext, true)) + case T_Constraint: { - successful = false; + Constraint *constr = (Constraint *) newConstraint; + + /* + * Currently, we only expect to see CONSTR_CHECK nodes + * arriving here (see the preprocessing done in + * parser/analyze.c). Use a switch anyway to make it + * easier to add more code later. + */ + switch (constr->contype) + { + case CONSTR_CHECK: + { + List *newcons; + List *lcon; + + /* + * Call AddRelationRawConstraints to do the work. + * It returns a list of cooked constraints. + */ + newcons = AddRelationRawConstraints(rel, NIL, + makeList1(constr)); + /* Add each constraint to Phase 3's queue */ + foreach(lcon, newcons) + { + CookedConstraint *ccon = (CookedConstraint *) lfirst(lcon); + NewConstraint *newcon; + + newcon = (NewConstraint *) palloc0(sizeof(NewConstraint)); + newcon->name = ccon->name; + newcon->contype = ccon->contype; + newcon->attnum = ccon->attnum; + /* ExecQual wants implicit-AND format */ + newcon->qual = (Node *) + make_ands_implicit((Expr *) ccon->expr); + + tab->constraints = lappend(tab->constraints, + newcon); + } + break; + } + default: + elog(ERROR, "unrecognized constraint type: %d", + (int) constr->contype); + } break; } - ResetExprContext(econtext); + case T_FkConstraint: + { + FkConstraint *fkconstraint = (FkConstraint *) newConstraint; + + /* + * Assign or validate constraint name + */ + if (fkconstraint->constr_name) + { + if (ConstraintNameIsUsed(CONSTRAINT_RELATION, + RelationGetRelid(rel), + RelationGetNamespace(rel), + fkconstraint->constr_name)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("constraint \"%s\" for relation \"%s\" already exists", + fkconstraint->constr_name, + RelationGetRelationName(rel)))); + } + else + fkconstraint->constr_name = + GenerateConstraintName(CONSTRAINT_RELATION, + RelationGetRelid(rel), + RelationGetNamespace(rel), + &tab->constr_name_ctr); + + ATAddForeignKeyConstraint(tab, rel, fkconstraint); + + break; + } + default: + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(newConstraint)); } - - heap_endscan(scan); - - pfree(slot); - FreeExecutorState(estate); - - if (!successful) - ereport(ERROR, - (errcode(ERRCODE_CHECK_VIOLATION), - errmsg("check constraint \"%s\" is violated by some row", - constr->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)); } /* * Add a foreign-key constraint to a single table * - * Subroutine for AlterTableAddConstraint. Must already hold exclusive + * Subroutine for ATExecAddConstraint. Must already hold exclusive * lock on the rel, and have done appropriate validity/permissions checks * for it. */ static void -AlterTableAddForeignKeyConstraint(Relation rel, FkConstraint *fkconstraint) +ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, + FkConstraint *fkconstraint) { Relation pkrel; AclResult aclresult; @@ -3124,11 +3668,21 @@ AlterTableAddForeignKeyConstraint(Relation rel, FkConstraint *fkconstraint) } /* - * Check that the constraint is satisfied by existing rows (we can - * skip this during table creation). + * Tell Phase 3 to check that the constraint is satisfied by existing rows + * (we can skip this during table creation). */ if (!fkconstraint->skip_validation) - validateForeignKeyConstraint(fkconstraint, rel, pkrel); + { + NewConstraint *newcon; + + newcon = (NewConstraint *) palloc0(sizeof(NewConstraint)); + newcon->name = fkconstraint->constr_name; + newcon->contype = CONSTR_FOREIGN; + newcon->refrelid = RelationGetRelid(pkrel); + newcon->qual = (Node *) fkconstraint; + + tab->constraints = lappend(tab->constraints, newcon); + } /* * Record the FK constraint in pg_constraint. @@ -3728,94 +4282,626 @@ fkMatchTypeToString(char match_type) /* * ALTER TABLE DROP CONSTRAINT */ -void -AlterTableDropConstraint(Oid myrelid, bool recurse, - const char *constrName, - DropBehavior behavior) +static void +ATPrepDropConstraint(List **wqueue, Relation rel, + bool recurse, AlterTableCmd *cmd) { - Relation rel; - int deleted = 0; - /* - * Acquire an exclusive lock on the target relation for the duration - * of the operation. - */ - rel = heap_open(myrelid, AccessExclusiveLock); - - /* Disallow DROP CONSTRAINT on views, indexes, sequences, etc */ - if (rel->rd_rel->relkind != RELKIND_RELATION) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is not a table", - RelationGetRelationName(rel)))); - - /* Permissions checks */ - if (!pg_class_ownercheck(myrelid, GetUserId())) - aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, - RelationGetRelationName(rel)); - - if (!allowSystemTableMods && IsSystemRelation(rel)) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("permission denied: \"%s\" is a system catalog", - RelationGetRelationName(rel)))); - - /* - * Process child tables if requested. + * We don't want errors or noise from child tables, so we have to pass + * down a modified command. */ if (recurse) { - List *child, - *children; + AlterTableCmd *childCmd = copyObject(cmd); - /* This routine is actually in the planner */ - children = find_all_inheritors(myrelid); + childCmd->subtype = AT_DropConstraintQuietly; + ATSimpleRecursion(wqueue, rel, childCmd, recurse); + } +} - /* - * 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) +static void +ATExecDropConstraint(Relation rel, const char *constrName, + DropBehavior behavior, bool quiet) +{ + int deleted; + + deleted = RemoveRelConstraints(rel, constrName, behavior); + + if (!quiet) + { + /* If zero constraints deleted, complain */ + if (deleted == 0) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("constraint \"%s\" does not exist", + constrName))); + /* Otherwise if more than one constraint deleted, notify */ + else if (deleted > 1) + ereport(NOTICE, + (errmsg("multiple constraints named \"%s\" were dropped", + constrName))); + } +} + +/* + * ALTER COLUMN TYPE + */ +static void +ATPrepAlterColumnType(List **wqueue, + AlteredTableInfo *tab, Relation rel, + bool recurse, bool recursing, + AlterTableCmd *cmd) +{ + char *colName = cmd->name; + TypeName *typename = (TypeName *) cmd->def; + HeapTuple tuple; + Form_pg_attribute attTup; + AttrNumber attnum; + Oid targettype; + Node *transform; + NewColumnValue *newval; + ParseState *pstate = make_parsestate(NULL); + + /* lookup the attribute so we can check inheritance status */ + tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + colName, RelationGetRelationName(rel)))); + attTup = (Form_pg_attribute) GETSTRUCT(tuple); + attnum = attTup->attnum; + + /* Can't alter a system attribute */ + if (attnum <= 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot alter system column \"%s\"", + colName))); + + /* Don't alter inherited columns */ + if (attTup->attinhcount > 0 && !recursing) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot alter inherited column \"%s\"", + colName))); + + /* Look up the target type */ + targettype = LookupTypeName(typename); + if (!OidIsValid(targettype)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("type \"%s\" does not exist", + TypeNameToString(typename)))); + + /* make sure datatype is legal for a column */ + CheckAttributeType(colName, targettype); + + /* + * Set up an expression to transform the old data value to the new type. + * If a USING option was given, transform and use that expression, else + * just take the old value and try to coerce it. We do this first so + * that type incompatibility can be detected before we waste effort, + * and because we need the expression to be parsed against the original + * table rowtype. + */ + if (cmd->transform) + { + RangeTblEntry *rte; + + /* Expression must be able to access vars of old table */ + rte = addRangeTableEntryForRelation(pstate, + RelationGetRelid(rel), + makeAlias(RelationGetRelationName(rel), NIL), + false, + true); + addRTEtoQuery(pstate, rte, false, true); + + transform = transformExpr(pstate, cmd->transform); + + /* It can't return a set */ + if (expression_returns_set(transform)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("transform expression must not return a set"))); + + /* No subplans or aggregates, either... */ + if (pstate->p_hasSubLinks) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot use subquery in transform expression"))); + if (pstate->p_hasAggs) + ereport(ERROR, + (errcode(ERRCODE_GROUPING_ERROR), + errmsg("cannot use aggregate function in transform expression"))); + } + else + { + transform = (Node *) makeVar(1, attnum, + attTup->atttypid, attTup->atttypmod, + 0); + } + + transform = coerce_to_target_type(pstate, + transform, exprType(transform), + targettype, typename->typmod, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST); + if (transform == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("column \"%s\" cannot be cast to type \"%s\"", + colName, TypeNameToString(typename)))); + + /* + * Add a work queue item to make ATRewriteTable update the column + * contents. + */ + newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue)); + newval->attnum = attnum; + newval->expr = (Expr *) transform; + + tab->newvals = lappend(tab->newvals, newval); + + ReleaseSysCache(tuple); + + /* + * The recursion case is handled by ATSimpleRecursion. However, + * if we are told not to recurse, there had better not be any + * child tables; else the alter would put them out of step. + */ + if (recurse) + ATSimpleRecursion(wqueue, rel, cmd, recurse); + else if (!recursing && + find_inheritance_children(RelationGetRelid(rel)) != NIL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("type of inherited column \"%s\" must be changed in child tables too", + colName))); +} + +static void +ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, + const char *colName, TypeName *typename) +{ + HeapTuple heapTup; + Form_pg_attribute attTup; + AttrNumber attnum; + HeapTuple typeTuple; + Form_pg_type tform; + Oid targettype; + Node *defaultexpr; + Relation attrelation; + Relation depRel; + ScanKeyData key[3]; + SysScanDesc scan; + HeapTuple depTup; + + attrelation = heap_openr(AttributeRelationName, RowExclusiveLock); + + /* Look up the target column */ + heapTup = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); + if (!HeapTupleIsValid(heapTup)) /* shouldn't happen */ + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + colName, RelationGetRelationName(rel)))); + attTup = (Form_pg_attribute) GETSTRUCT(heapTup); + attnum = attTup->attnum; + + /* Check for multiple ALTER TYPE on same column --- can't cope */ + if (attTup->atttypid != tab->oldDesc->attrs[attnum-1]->atttypid || + attTup->atttypmod != tab->oldDesc->attrs[attnum-1]->atttypmod) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot alter type of column \"%s\" twice", + colName))); + + /* Look up the target type (should not fail, since prep found it) */ + typeTuple = typenameType(typename); + tform = (Form_pg_type) GETSTRUCT(typeTuple); + targettype = HeapTupleGetOid(typeTuple); + + /* + * If there is a default expression for the column, get it and ensure + * we can coerce it to the new datatype. (We must do this before + * changing the column type, because build_column_default itself will + * try to coerce, and will not issue the error message we want if it + * fails.) + */ + if (attTup->atthasdef) + { + defaultexpr = build_column_default(rel, attnum); + Assert(defaultexpr); + defaultexpr = coerce_to_target_type(NULL, /* no UNKNOWN params */ + defaultexpr, exprType(defaultexpr), + targettype, typename->typmod, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST); + if (defaultexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("default for column \"%s\" cannot be cast to type \"%s\"", + colName, TypeNameToString(typename)))); + } + else + defaultexpr = NULL; + + /* + * Find everything that depends on the column (constraints, indexes, etc), + * and record enough information to let us recreate the objects. + * + * The actual recreation does not happen here, but only after we have + * performed all the individual ALTER TYPE operations. We have to save + * the info before executing ALTER TYPE, though, else the deparser will + * get confused. + * + * There could be multiple entries for the same object, so we must check + * to ensure we process each one only once. Note: we assume that an index + * that implements a constraint will not show a direct dependency on the + * column. + */ + depRel = heap_openr(DependRelationName, RowExclusiveLock); + + ScanKeyInit(&key[0], + Anum_pg_depend_refclassid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelOid_pg_class)); + ScanKeyInit(&key[1], + Anum_pg_depend_refobjid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(rel))); + ScanKeyInit(&key[2], + Anum_pg_depend_refobjsubid, + BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum((int32) attnum)); + + scan = systable_beginscan(depRel, DependReferenceIndex, true, + SnapshotNow, 3, key); + + while (HeapTupleIsValid(depTup = systable_getnext(scan))) + { + Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup); + ObjectAddress foundObject; + + /* We don't expect any PIN dependencies on columns */ + if (foundDep->deptype == DEPENDENCY_PIN) + elog(ERROR, "cannot alter type of a pinned column"); + + foundObject.classId = foundDep->classid; + foundObject.objectId = foundDep->objid; + foundObject.objectSubId = foundDep->objsubid; + + switch (getObjectClass(&foundObject)) { - Oid childrelid = lfirsto(child); - Relation inhrel; + case OCLASS_CLASS: + { + char relKind = get_rel_relkind(foundObject.objectId); - if (childrelid == myrelid) - continue; - inhrel = heap_open(childrelid, AccessExclusiveLock); - /* do NOT count child constraints in deleted. */ - RemoveRelConstraints(inhrel, constrName, behavior); - heap_close(inhrel, NoLock); + if (relKind == RELKIND_INDEX) + { + Assert(foundObject.objectSubId == 0); + if (!oidMember(foundObject.objectId, tab->changedIndexOids)) + { + tab->changedIndexOids = lappendo(tab->changedIndexOids, + foundObject.objectId); + tab->changedIndexDefs = lappend(tab->changedIndexDefs, + pg_get_indexdef_string(foundObject.objectId)); + } + } + else if (relKind == RELKIND_SEQUENCE) + { + /* + * This must be a SERIAL column's sequence. We need not + * do anything to it. + */ + Assert(foundObject.objectSubId == 0); + } + else + { + /* Not expecting any other direct dependencies... */ + elog(ERROR, "unexpected object depending on column: %s", + getObjectDescription(&foundObject)); + } + break; + } + + case OCLASS_CONSTRAINT: + Assert(foundObject.objectSubId == 0); + if (!oidMember(foundObject.objectId, tab->changedConstraintOids)) + { + tab->changedConstraintOids = lappendo(tab->changedConstraintOids, + foundObject.objectId); + tab->changedConstraintDefs = lappend(tab->changedConstraintDefs, + pg_get_constraintdef_string(foundObject.objectId)); + } + break; + + case OCLASS_REWRITE: + /* XXX someday see if we can cope with revising views */ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot alter type of a column used by a view or rule"), + errdetail("%s depends on column \"%s\"", + getObjectDescription(&foundObject), + colName))); + break; + + case OCLASS_DEFAULT: + /* + * Ignore the column's default expression, since we will fix + * it below. + */ + Assert(defaultexpr); + break; + + case OCLASS_PROC: + case OCLASS_TYPE: + case OCLASS_CAST: + case OCLASS_CONVERSION: + case OCLASS_LANGUAGE: + case OCLASS_OPERATOR: + case OCLASS_OPCLASS: + case OCLASS_TRIGGER: + case OCLASS_SCHEMA: + /* + * We don't expect any of these sorts of objects to depend + * on a column. + */ + elog(ERROR, "unexpected object depending on column: %s", + getObjectDescription(&foundObject)); + break; + + default: + elog(ERROR, "unrecognized object class: %u", + foundObject.classId); } } + systable_endscan(scan); + /* - * Now do the thing on this relation. + * Now scan for dependencies of this column on other things. The only + * thing we should find is the dependency on the column datatype, + * which we want to remove. */ - deleted += RemoveRelConstraints(rel, constrName, behavior); + ScanKeyInit(&key[0], + Anum_pg_depend_classid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelOid_pg_class)); + ScanKeyInit(&key[1], + Anum_pg_depend_objid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(rel))); + ScanKeyInit(&key[2], + Anum_pg_depend_objsubid, + BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum((int32) attnum)); - /* Close the target relation */ - heap_close(rel, NoLock); + scan = systable_beginscan(depRel, DependDependerIndex, true, + SnapshotNow, 3, key); - /* If zero constraints deleted, complain */ - if (deleted == 0) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("constraint \"%s\" does not exist", - constrName))); - /* Otherwise if more than one constraint deleted, notify */ - else if (deleted > 1) - ereport(NOTICE, - (errmsg("multiple constraints named \"%s\" were dropped", - constrName))); + while (HeapTupleIsValid(depTup = systable_getnext(scan))) + { + Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup); + + if (foundDep->deptype != DEPENDENCY_NORMAL) + elog(ERROR, "found unexpected dependency type '%c'", + foundDep->deptype); + if (foundDep->classid != RelOid_pg_type || + foundDep->objid != attTup->atttypid) + elog(ERROR, "found unexpected dependency for column"); + + simple_heap_delete(depRel, &depTup->t_self); + } + + systable_endscan(scan); + + heap_close(depRel, RowExclusiveLock); + + /* + * Here we go --- change the recorded column type. (Note heapTup is + * a copy of the syscache entry, so okay to scribble on.) + */ + attTup->atttypid = targettype; + attTup->atttypmod = typename->typmod; + attTup->attndims = length(typename->arrayBounds); + attTup->attlen = tform->typlen; + attTup->attbyval = tform->typbyval; + attTup->attalign = tform->typalign; + attTup->attstorage = tform->typstorage; + + ReleaseSysCache(typeTuple); + + simple_heap_update(attrelation, &heapTup->t_self, heapTup); + + /* keep system catalog indexes current */ + CatalogUpdateIndexes(attrelation, heapTup); + + heap_close(attrelation, RowExclusiveLock); + + /* Install dependency on new datatype */ + add_column_datatype_dependency(RelationGetRelid(rel), attnum, targettype); + + /* Drop any pg_statistic entry for the column, since it's now wrong type */ + RemoveStatistics(rel, attnum); + + /* + * Update the default, if present, by brute force --- remove and re-add + * the default. Probably unsafe to take shortcuts, since the new version + * may well have additional dependencies. (It's okay to do this now, + * rather than after other ALTER TYPE commands, since the default won't + * depend on other column types.) + */ + if (defaultexpr) + { + /* Must make new row visible since it will be updated again */ + CommandCounterIncrement(); + + /* + * We use RESTRICT here for safety, but at present we do not expect + * anything to depend on the default. + */ + RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, true); + + StoreAttrDefault(rel, attnum, nodeToString(defaultexpr)); + } + + /* Cleanup */ + heap_freetuple(heapTup); } +/* + * Cleanup after we've finished all the ALTER TYPE operations for a + * particular relation. We have to drop and recreate all the indexes + * and constraints that depend on the altered columns. + */ +static void +ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab) +{ + ObjectAddress obj; + List *l; + + /* + * Re-parse the index and constraint definitions, and attach them to + * the appropriate work queue entries. We do this before dropping + * because in the case of a FOREIGN KEY constraint, we might not yet + * have exclusive lock on the table the constraint is attached to, + * and we need to get that before dropping. It's safe because the + * parser won't actually look at the catalogs to detect the existing + * entry. + */ + foreach(l, tab->changedIndexDefs) + { + ATPostAlterTypeParse((char *) lfirst(l), wqueue); + } + foreach(l, tab->changedConstraintDefs) + { + ATPostAlterTypeParse((char *) lfirst(l), wqueue); + } + + /* + * Now we can drop the existing constraints and indexes --- constraints + * first, since some of them might depend on the indexes. It should be + * okay to use DROP_RESTRICT here, since nothing else should be depending + * on these objects. + */ + if (tab->changedConstraintOids) + obj.classId = get_system_catalog_relid(ConstraintRelationName); + foreach(l, tab->changedConstraintOids) + { + obj.objectId = lfirsto(l); + obj.objectSubId = 0; + performDeletion(&obj, DROP_RESTRICT); + } + + obj.classId = RelOid_pg_class; + foreach(l, tab->changedIndexOids) + { + obj.objectId = lfirsto(l); + obj.objectSubId = 0; + performDeletion(&obj, DROP_RESTRICT); + } + + /* + * The objects will get recreated during subsequent passes over the + * work queue. + */ +} + +static void +ATPostAlterTypeParse(char *cmd, List **wqueue) +{ + List *raw_parsetree_list; + List *querytree_list; + List *list_item; + + /* + * We expect that we only have to do raw parsing and parse analysis, not + * any rule rewriting, since these will all be utility statements. + */ + raw_parsetree_list = raw_parser(cmd); + querytree_list = NIL; + foreach(list_item, raw_parsetree_list) + { + Node *parsetree = (Node *) lfirst(list_item); + + querytree_list = nconc(querytree_list, + parse_analyze(parsetree, NULL, 0)); + } + + /* + * Attach each generated command to the proper place in the work queue. + * Note this could result in creation of entirely new work-queue entries. + */ + foreach(list_item, querytree_list) + { + Query *query = (Query *) lfirst(list_item); + Relation rel; + AlteredTableInfo *tab; + + Assert(IsA(query, Query)); + Assert(query->commandType == CMD_UTILITY); + switch (nodeTag(query->utilityStmt)) + { + case T_IndexStmt: + { + IndexStmt *stmt = (IndexStmt *) query->utilityStmt; + AlterTableCmd *newcmd; + + rel = relation_openrv(stmt->relation, AccessExclusiveLock); + tab = ATGetQueueEntry(wqueue, rel); + newcmd = makeNode(AlterTableCmd); + newcmd->subtype = AT_ReAddIndex; + newcmd->def = (Node *) stmt; + tab->subcmds[AT_PASS_OLD_INDEX] = + lappend(tab->subcmds[AT_PASS_OLD_INDEX], newcmd); + relation_close(rel, NoLock); + break; + } + case T_AlterTableStmt: + { + AlterTableStmt *stmt = (AlterTableStmt *) query->utilityStmt; + List *lcmd; + + rel = relation_openrv(stmt->relation, AccessExclusiveLock); + tab = ATGetQueueEntry(wqueue, rel); + foreach(lcmd, stmt->cmds) + { + AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd); + + switch (cmd->subtype) + { + case AT_AddIndex: + cmd->subtype = AT_ReAddIndex; + tab->subcmds[AT_PASS_OLD_INDEX] = + lappend(tab->subcmds[AT_PASS_OLD_INDEX], cmd); + break; + case AT_AddConstraint: + tab->subcmds[AT_PASS_OLD_CONSTR] = + lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd); + break; + default: + elog(ERROR, "unexpected statement type: %d", + (int) cmd->subtype); + } + } + relation_close(rel, NoLock); + break; + } + default: + elog(ERROR, "unexpected statement type: %d", + (int) nodeTag(query->utilityStmt)); + } + } +} + + /* * ALTER TABLE OWNER */ -void -AlterTableOwner(Oid relationOid, int32 newOwnerSysId) +static void +ATExecChangeOwner(Oid relationOid, int32 newOwnerSysId) { Relation target_rel; Relation class_rel; @@ -3879,7 +4965,7 @@ AlterTableOwner(Oid relationOid, int32 newOwnerSysId) /* For each index, recursively change its ownership */ foreach(i, index_oid_list) - AlterTableOwner(lfirsto(i), newOwnerSysId); + ATExecChangeOwner(lfirsto(i), newOwnerSysId); freeList(index_oid_list); } @@ -3888,7 +4974,7 @@ AlterTableOwner(Oid relationOid, int32 newOwnerSysId) { /* If it has a toast table, recurse to change its ownership */ if (tuple_class->reltoastrelid != InvalidOid) - AlterTableOwner(tuple_class->reltoastrelid, newOwnerSysId); + ATExecChangeOwner(tuple_class->reltoastrelid, newOwnerSysId); } heap_freetuple(tuple); @@ -3901,25 +4987,22 @@ AlterTableOwner(Oid relationOid, int32 newOwnerSysId) * * The only thing we have to do is to change the indisclustered bits. */ -void -AlterTableClusterOn(Oid relOid, const char *indexName) +static void +ATExecClusterOn(Relation rel, const char *indexName) { - Relation rel, - pg_index; + Relation pg_index; List *index; Oid indexOid; HeapTuple indexTuple; Form_pg_index indexForm; - rel = heap_open(relOid, AccessExclusiveLock); - indexOid = get_relname_relid(indexName, rel->rd_rel->relnamespace); if (!OidIsValid(indexOid)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("index \"%s\" for table \"%s\" does not exist", - indexName, NameStr(rel->rd_rel->relname)))); + indexName, RelationGetRelationName(rel)))); indexTuple = SearchSysCache(INDEXRELID, ObjectIdGetDatum(indexOid), @@ -3935,10 +5018,11 @@ AlterTableClusterOn(Oid relOid, const char *indexName) if (indexForm->indisclustered) { ReleaseSysCache(indexTuple); - heap_close(rel, NoLock); return; } + ReleaseSysCache(indexTuple); + pg_index = heap_openr(IndexRelationName, RowExclusiveLock); /* @@ -3977,14 +5061,15 @@ AlterTableClusterOn(Oid relOid, const char *indexName) } heap_close(pg_index, RowExclusiveLock); - - ReleaseSysCache(indexTuple); - - heap_close(rel, NoLock); /* close rel, but keep lock till commit */ } /* * ALTER TABLE CREATE TOAST TABLE + * + * Note: this is also invoked from outside this module; in such cases we + * expect the caller to have verified that the relation is a table and we + * have all the right permissions. Callers expect this function + * to end with CommandCounterIncrement if it makes any changes. */ void AlterTableCreateToastTable(Oid relOid, bool silent) @@ -4005,21 +5090,11 @@ AlterTableCreateToastTable(Oid relOid, bool silent) /* * Grab an exclusive lock on the target table, which we will NOT - * release until end of transaction. + * release until end of transaction. (This is probably redundant + * in all present uses...) */ rel = heap_open(relOid, AccessExclusiveLock); - if (rel->rd_rel->relkind != RELKIND_RELATION) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is not a table", - RelationGetRelationName(rel)))); - - /* Permissions checks */ - if (!pg_class_ownercheck(relOid, GetUserId())) - aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, - RelationGetRelationName(rel)); - /* * Toast table is shared if and only if its parent is. * @@ -4149,7 +5224,7 @@ AlterTableCreateToastTable(Oid relOid, bool silent) toast_idxid = index_create(toast_relid, toast_idxname, indexInfo, BTREE_AM_OID, classObjectId, - true, false, true); + true, false, true, false); /* * Update toast rel's pg_class entry to show that it has an index. The diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index c7d6193280..1466be98cb 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.279 2004/03/17 20:48:42 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.280 2004/05/05 04:48:45 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1674,10 +1674,21 @@ _copyAlterTableStmt(AlterTableStmt *from) { AlterTableStmt *newnode = makeNode(AlterTableStmt); - COPY_SCALAR_FIELD(subtype); COPY_NODE_FIELD(relation); + COPY_NODE_FIELD(cmds); + + return newnode; +} + +static AlterTableCmd * +_copyAlterTableCmd(AlterTableCmd *from) +{ + AlterTableCmd *newnode = makeNode(AlterTableCmd); + + COPY_SCALAR_FIELD(subtype); COPY_STRING_FIELD(name); COPY_NODE_FIELD(def); + COPY_NODE_FIELD(transform); COPY_SCALAR_FIELD(behavior); return newnode; @@ -2773,6 +2784,9 @@ copyObject(void *from) case T_AlterTableStmt: retval = _copyAlterTableStmt(from); break; + case T_AlterTableCmd: + retval = _copyAlterTableCmd(from); + break; case T_AlterDomainStmt: retval = _copyAlterDomainStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 900d98dc8c..eab30d122c 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -18,7 +18,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.218 2004/03/17 20:48:42 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.219 2004/05/05 04:48:45 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -710,10 +710,19 @@ _equalSetOperationStmt(SetOperationStmt *a, SetOperationStmt *b) static bool _equalAlterTableStmt(AlterTableStmt *a, AlterTableStmt *b) { - COMPARE_SCALAR_FIELD(subtype); COMPARE_NODE_FIELD(relation); + COMPARE_NODE_FIELD(cmds); + + return true; +} + +static bool +_equalAlterTableCmd(AlterTableCmd *a, AlterTableCmd *b) +{ + COMPARE_SCALAR_FIELD(subtype); COMPARE_STRING_FIELD(name); COMPARE_NODE_FIELD(def); + COMPARE_NODE_FIELD(transform); COMPARE_SCALAR_FIELD(behavior); return true; @@ -1846,6 +1855,9 @@ equal(void *a, void *b) case T_AlterTableStmt: retval = _equalAlterTableStmt(a, b); break; + case T_AlterTableCmd: + retval = _equalAlterTableCmd(a, b); + break; case T_AlterDomainStmt: retval = _equalAlterDomainStmt(a, b); break; diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 2330bf18d4..4a251b63de 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.298 2004/04/02 21:05:32 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.299 2004/05/05 04:48:46 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -77,7 +77,7 @@ typedef struct RangeVar *relation; /* relation to create */ List *inhRelations; /* relations to inherit from */ bool hasoids; /* does relation have an OID column? */ - Oid relOid; /* OID of table, if ALTER TABLE case */ + bool isalter; /* true if altering existing table */ List *columns; /* ColumnDef items */ List *ckconstraints; /* CHECK constraints */ List *fkconstraints; /* FOREIGN KEY constraints */ @@ -131,18 +131,17 @@ static void transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt); static void transformFKConstraints(ParseState *pstate, CreateStmtContext *cxt, + bool skipValidation, bool isAddConstraint); static void applyColumnNames(List *dst, List *src); static List *getSetColTypes(ParseState *pstate, Node *node); static void transformForUpdate(Query *qry, List *forUpdate); static void transformConstraintAttrs(List *constraintList); static void transformColumnType(ParseState *pstate, ColumnDef *column); -static bool relationHasPrimaryKey(Oid relationOid); static void release_pstate_resources(ParseState *pstate); static FromExpr *makeFromExpr(List *fromlist, Node *quals); static bool check_parameter_resolution_walker(Node *node, check_parameter_resolution_context *context); -static char *makeObjectName(char *name1, char *name2, char *typename); /* @@ -346,7 +345,8 @@ transformStmt(ParseState *pstate, Node *parseTree, break; case T_AlterTableStmt: - result = transformAlterTableStmt(pstate, (AlterTableStmt *) parseTree, + result = transformAlterTableStmt(pstate, + (AlterTableStmt *) parseTree, extras_before, extras_after); break; @@ -733,8 +733,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt, * from the truncated characters. Currently it seems best to keep it simple, * so that the generated names are easily predictable by a person. */ -static char * -makeObjectName(char *name1, char *name2, char *typename) +char * +makeObjectName(const char *name1, const char *name2, const char *typename) { char *name; int overhead = 0; /* chars needed for type and underscores */ @@ -795,48 +795,6 @@ makeObjectName(char *name1, char *name2, char *typename) return name; } -static char * -CreateIndexName(char *table_name, char *column_name, - char *label, List *indices) -{ - int pass = 0; - char *iname = NULL; - List *ilist; - char typename[NAMEDATALEN]; - - /* - * The type name for makeObjectName is label, or labelN if that's - * necessary to prevent collisions among multiple indexes for the same - * table. Note there is no check for collisions with already-existing - * indexes, only among the indexes we're about to create now; this - * ought to be improved someday. - */ - strncpy(typename, label, sizeof(typename)); - - for (;;) - { - iname = makeObjectName(table_name, column_name, typename); - - foreach(ilist, indices) - { - IndexStmt *index = lfirst(ilist); - - if (index->idxname != NULL && - strcmp(iname, index->idxname) == 0) - break; - } - /* ran through entire list? then no name conflict found so done */ - if (ilist == NIL) - break; - - /* found a conflict, so try a new name component */ - pfree(iname); - snprintf(typename, sizeof(typename), "%s%d", label, ++pass); - } - - return iname; -} - /* * transformCreateStmt - * transforms the "create table" statement @@ -857,7 +815,7 @@ transformCreateStmt(ParseState *pstate, CreateStmt *stmt, cxt.stmtType = "CREATE TABLE"; cxt.relation = stmt->relation; cxt.inhRelations = stmt->inhRelations; - cxt.relOid = InvalidOid; + cxt.isalter = false; cxt.columns = NIL; cxt.ckconstraints = NIL; cxt.fkconstraints = NIL; @@ -914,7 +872,7 @@ transformCreateStmt(ParseState *pstate, CreateStmt *stmt, /* * Postprocess foreign-key constraints. */ - transformFKConstraints(pstate, &cxt, false); + transformFKConstraints(pstate, &cxt, true, false); /* * Output results. @@ -1326,24 +1284,23 @@ transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt) index->primary = (constraint->contype == CONSTR_PRIMARY); if (index->primary) { - /* In ALTER TABLE case, a primary index might already exist */ - if (cxt->pkey != NULL || - (OidIsValid(cxt->relOid) && - relationHasPrimaryKey(cxt->relOid))) + if (cxt->pkey != NULL) ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("multiple primary keys for table \"%s\" are not allowed", cxt->relation->relname))); cxt->pkey = index; + /* + * In ALTER TABLE case, a primary index might already exist, + * but DefineIndex will check for it. + */ } index->isconstraint = true; if (constraint->name != NULL) index->idxname = pstrdup(constraint->name); - else if (constraint->contype == CONSTR_PRIMARY) - index->idxname = makeObjectName(cxt->relation->relname, NULL, "pkey"); else - index->idxname = NULL; /* will set it later */ + index->idxname = NULL; /* DefineIndex will choose name */ index->relation = cxt->relation; index->accessMethod = DEFAULT_INDEX_TYPE; @@ -1431,25 +1388,14 @@ transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt) break; } } - else if (OidIsValid(cxt->relOid)) - { - /* ALTER TABLE case: does column already exist? */ - HeapTuple atttuple; - atttuple = SearchSysCacheAttName(cxt->relOid, key); - if (HeapTupleIsValid(atttuple)) - { - found = true; - - /* - * If it's not already NOT NULL, leave it to - * DefineIndex to fix later. - */ - ReleaseSysCache(atttuple); - } - } - - if (!found) + /* + * In the ALTER TABLE case, don't complain about index keys + * not created in the command; they may well exist already. + * DefineIndex will complain about them if not, and will also + * take care of marking them NOT NULL. + */ + if (!found && !cxt->isalter) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("column \"%s\" named in key does not exist", @@ -1537,51 +1483,36 @@ transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt) indexlist = lnext(indexlist); } - - /* - * Finally, select unique names for all not-previously-named indices, - * and display NOTICE messages. - * - * XXX in ALTER TABLE case, we fail to consider name collisions against - * pre-existing indexes. - */ - foreach(indexlist, cxt->alist) - { - index = lfirst(indexlist); - - if (index->idxname == NULL && index->indexParams != NIL) - { - iparam = (IndexElem *) lfirst(index->indexParams); - /* we should never see an expression item here */ - Assert(iparam->expr == NULL); - index->idxname = CreateIndexName(cxt->relation->relname, - iparam->name, - "key", - cxt->alist); - } - if (index->idxname == NULL) /* should not happen */ - elog(ERROR, "failed to make implicit index name"); - - ereport(NOTICE, - (errmsg("%s / %s%s will create implicit index \"%s\" for table \"%s\"", - cxt->stmtType, - (strcmp(cxt->stmtType, "ALTER TABLE") == 0) ? "ADD " : "", - (index->primary ? "PRIMARY KEY" : "UNIQUE"), - index->idxname, cxt->relation->relname))); - } } static void transformFKConstraints(ParseState *pstate, CreateStmtContext *cxt, - bool isAddConstraint) + bool skipValidation, bool isAddConstraint) { + List *fkclist; + if (cxt->fkconstraints == NIL) return; /* - * For ALTER TABLE ADD CONSTRAINT, nothing to do. For CREATE TABLE or - * ALTER TABLE ADD COLUMN, gin up an ALTER TABLE ADD CONSTRAINT - * command to execute after the basic command is complete. + * If CREATE TABLE or adding a column with NULL default, we can safely + * skip validation of the constraint. + */ + if (skipValidation) + { + foreach(fkclist, cxt->fkconstraints) + { + FkConstraint *fkconstraint = (FkConstraint *) lfirst(fkclist); + + fkconstraint->skip_validation = true; + } + } + + /* + * For CREATE TABLE or ALTER TABLE ADD COLUMN, gin up an ALTER TABLE + * ADD CONSTRAINT command to execute after the basic command is complete. + * (If called from ADD CONSTRAINT, that routine will add the FK constraints + * to its own subcommand list.) * * Note: the ADD CONSTRAINT command must also execute after any index * creation commands. Thus, this should run after @@ -1591,22 +1522,22 @@ transformFKConstraints(ParseState *pstate, CreateStmtContext *cxt, if (!isAddConstraint) { AlterTableStmt *alterstmt = makeNode(AlterTableStmt); - List *fkclist; - alterstmt->subtype = 'c'; /* preprocessed add constraint */ alterstmt->relation = cxt->relation; - alterstmt->name = NULL; - alterstmt->def = (Node *) cxt->fkconstraints; + alterstmt->cmds = NIL; - /* Don't need to scan the table contents in this case */ foreach(fkclist, cxt->fkconstraints) { FkConstraint *fkconstraint = (FkConstraint *) lfirst(fkclist); + AlterTableCmd *altercmd = makeNode(AlterTableCmd); - fkconstraint->skip_validation = true; + altercmd->subtype = AT_ProcessedConstraint; + altercmd->name = NULL; + altercmd->def = (Node *) fkconstraint; + alterstmt->cmds = lappend(alterstmt->cmds, altercmd); } - cxt->alist = lappend(cxt->alist, (Node *) alterstmt); + cxt->alist = lappend(cxt->alist, alterstmt); } } @@ -2554,111 +2485,158 @@ static Query * transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt, List **extras_before, List **extras_after) { - Relation rel; CreateStmtContext cxt; Query *qry; + List *lcmd, + *l; + List *newcmds = NIL; + bool skipValidation = true; + AlterTableCmd *newcmd; + + cxt.stmtType = "ALTER TABLE"; + cxt.relation = stmt->relation; + cxt.inhRelations = NIL; + cxt.isalter = true; + cxt.hasoids = false; /* need not be right */ + cxt.columns = NIL; + cxt.ckconstraints = NIL; + cxt.fkconstraints = NIL; + cxt.ixconstraints = NIL; + cxt.blist = NIL; + cxt.alist = NIL; + cxt.pkey = NULL; /* * The only subtypes that currently require parse transformation - * handling are 'A'dd column and Add 'C'onstraint. These largely + * handling are ADD COLUMN and ADD CONSTRAINT. These largely * re-use code from CREATE TABLE. - * - * If we need to do any parse transformation, get exclusive lock on the - * relation to make sure it won't change before we execute the - * command. */ - switch (stmt->subtype) + foreach(lcmd, stmt->cmds) { - case 'A': - rel = heap_openrv(stmt->relation, AccessExclusiveLock); + AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd); - cxt.stmtType = "ALTER TABLE"; - cxt.relation = stmt->relation; - cxt.inhRelations = NIL; - cxt.relOid = RelationGetRelid(rel); - cxt.hasoids = SearchSysCacheExists(ATTNUM, - ObjectIdGetDatum(cxt.relOid), - Int16GetDatum(ObjectIdAttributeNumber), - 0, 0); - cxt.columns = NIL; - cxt.ckconstraints = NIL; - cxt.fkconstraints = NIL; - cxt.ixconstraints = NIL; - cxt.blist = NIL; - cxt.alist = NIL; - cxt.pkey = NULL; + switch (cmd->subtype) + { + case AT_AddColumn: + { + ColumnDef *def = (ColumnDef *) cmd->def; - Assert(IsA(stmt->def, ColumnDef)); - transformColumnDefinition(pstate, &cxt, - (ColumnDef *) stmt->def); + Assert(IsA(cmd->def, ColumnDef)); + transformColumnDefinition(pstate, &cxt, + (ColumnDef *) cmd->def); - transformIndexConstraints(pstate, &cxt); - transformFKConstraints(pstate, &cxt, false); + /* + * If the column has a non-null default, we can't skip + * validation of foreign keys. + */ + if (((ColumnDef *) cmd->def)->raw_default != NULL) + skipValidation = false; - ((ColumnDef *) stmt->def)->constraints = cxt.ckconstraints; - *extras_before = nconc(*extras_before, cxt.blist); - *extras_after = nconc(cxt.alist, *extras_after); + newcmds = lappend(newcmds, cmd); - heap_close(rel, NoLock); /* close rel, keep lock */ - break; + /* + * Convert an ADD COLUMN ... NOT NULL constraint to a separate + * command + */ + if (def->is_not_null) + { + /* Remove NOT NULL from AddColumn */ + def->is_not_null = false; - case 'C': - rel = heap_openrv(stmt->relation, AccessExclusiveLock); + /* Add as a separate AlterTableCmd */ + newcmd = makeNode(AlterTableCmd); + newcmd->subtype = AT_SetNotNull; + newcmd->name = pstrdup(def->colname); + newcmds = lappend(newcmds, newcmd); + } - cxt.stmtType = "ALTER TABLE"; - cxt.relation = stmt->relation; - cxt.inhRelations = NIL; - cxt.relOid = RelationGetRelid(rel); - cxt.hasoids = SearchSysCacheExists(ATTNUM, - ObjectIdGetDatum(cxt.relOid), - Int16GetDatum(ObjectIdAttributeNumber), - 0, 0); - cxt.columns = NIL; - cxt.ckconstraints = NIL; - cxt.fkconstraints = NIL; - cxt.ixconstraints = NIL; - cxt.blist = NIL; - cxt.alist = NIL; - cxt.pkey = NULL; + /* + * All constraints are processed in other ways. + * Remove the original list + */ + def->constraints = NIL; - if (IsA(stmt->def, Constraint)) - transformTableConstraint(pstate, &cxt, - (Constraint *) stmt->def); - else if (IsA(stmt->def, FkConstraint)) - cxt.fkconstraints = lappend(cxt.fkconstraints, stmt->def); - else - elog(ERROR, "unrecognized node type: %d", - (int) nodeTag(stmt->def)); + break; + } + case AT_AddConstraint: + /* The original AddConstraint cmd node doesn't go to newcmds */ - transformIndexConstraints(pstate, &cxt); - transformFKConstraints(pstate, &cxt, true); + if (IsA(cmd->def, Constraint)) + transformTableConstraint(pstate, &cxt, + (Constraint *) cmd->def); + else if (IsA(cmd->def, FkConstraint)) + { + cxt.fkconstraints = lappend(cxt.fkconstraints, cmd->def); + skipValidation = false; + } + else + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(cmd->def)); + break; - Assert(cxt.columns == NIL); - /* fkconstraints should be put into my own stmt in this case */ - stmt->def = (Node *) nconc(cxt.ckconstraints, cxt.fkconstraints); - *extras_before = nconc(*extras_before, cxt.blist); - *extras_after = nconc(cxt.alist, *extras_after); + case AT_ProcessedConstraint: - heap_close(rel, NoLock); /* close rel, keep lock */ - break; + /* + * Already-transformed ADD CONSTRAINT, so just make it look + * like the standard case. + */ + cmd->subtype = AT_AddConstraint; + newcmds = lappend(newcmds, cmd); + break; - case 'c': - - /* - * Already-transformed ADD CONSTRAINT, so just make it look - * like the standard case. - */ - stmt->subtype = 'C'; - break; - - default: - break; + default: + newcmds = lappend(newcmds, cmd); + break; + } } + /* Postprocess index and FK constraints */ + transformIndexConstraints(pstate, &cxt); + + transformFKConstraints(pstate, &cxt, skipValidation, true); + + /* + * Push any index-creation commands into the ALTER, so that + * they can be scheduled nicely by tablecmds.c. + */ + foreach(l, cxt.alist) + { + Node *idxstmt = (Node *) lfirst(l); + + Assert(IsA(idxstmt, IndexStmt)); + newcmd = makeNode(AlterTableCmd); + newcmd->subtype = AT_AddIndex; + newcmd->def = idxstmt; + newcmds = lappend(newcmds, newcmd); + } + cxt.alist = NIL; + + /* Append any CHECK or FK constraints to the commands list */ + foreach(l, cxt.ckconstraints) + { + newcmd = makeNode(AlterTableCmd); + newcmd->subtype = AT_AddConstraint; + newcmd->def = (Node *) lfirst(l); + newcmds = lappend(newcmds, newcmd); + } + foreach(l, cxt.fkconstraints) + { + newcmd = makeNode(AlterTableCmd); + newcmd->subtype = AT_AddConstraint; + newcmd->def = (Node *) lfirst(l); + newcmds = lappend(newcmds, newcmd); + } + + /* Update statement's commands list */ + stmt->cmds = newcmds; + qry = makeNode(Query); qry->commandType = CMD_UTILITY; qry->utilityStmt = (Node *) stmt; + *extras_before = nconc(*extras_before, cxt.blist); + *extras_after = nconc(cxt.alist, *extras_after); + return qry; } @@ -2946,51 +2924,6 @@ transformForUpdate(Query *qry, List *forUpdate) } -/* - * relationHasPrimaryKey - - * - * See whether an existing relation has a primary key. - */ -static bool -relationHasPrimaryKey(Oid relationOid) -{ - bool result = false; - Relation rel; - List *indexoidlist, - *indexoidscan; - - rel = heap_open(relationOid, AccessShareLock); - - /* - * Get the list of index OIDs for the table from the relcache, and - * look up each one in the pg_index syscache until we find one marked - * primary key (hopefully there isn't more than one such). - */ - indexoidlist = RelationGetIndexList(rel); - - foreach(indexoidscan, indexoidlist) - { - Oid indexoid = lfirsto(indexoidscan); - HeapTuple indexTuple; - - indexTuple = SearchSysCache(INDEXRELID, - ObjectIdGetDatum(indexoid), - 0, 0, 0); - if (!HeapTupleIsValid(indexTuple)) /* should not happen */ - elog(ERROR, "cache lookup failed for index %u", indexoid); - result = ((Form_pg_index) GETSTRUCT(indexTuple))->indisprimary; - ReleaseSysCache(indexTuple); - if (result) - break; - } - - freeList(indexoidlist); - - heap_close(rel, AccessShareLock); - - return result; -} - /* * Preprocess a list of column constraint clauses * to attach constraint attributes to their primary constraint nodes diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index de8597ed9e..64825893bc 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.452 2004/04/21 00:34:18 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.453 2004/05/05 04:48:46 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -158,9 +158,12 @@ static void doNegateFloat(Value *v); %type select_no_parens select_with_parens select_clause simple_select -%type alter_column_default opclass_item +%type alter_column_default opclass_item alter_using %type add_drop +%type alter_table_cmd +%type alter_table_cmds + %type opt_drop_behavior %type createdb_opt_list copy_opt_list @@ -199,7 +202,7 @@ static void doNegateFloat(Value *v); %type qualified_name OptConstrFromTable -%type all_Op MathOp opt_name SpecialRuleRelation +%type all_Op MathOp SpecialRuleRelation %type iso_level opt_encoding %type grantee @@ -1127,127 +1130,139 @@ CheckPointStmt: *****************************************************************************/ AlterTableStmt: - /* ALTER TABLE ADD [COLUMN] */ - ALTER TABLE relation_expr ADD opt_column columnDef + ALTER TABLE relation_expr alter_table_cmds { AlterTableStmt *n = makeNode(AlterTableStmt); - n->subtype = 'A'; n->relation = $3; - n->def = $6; + n->cmds = $4; + $$ = (Node *)n; + } + ; + +alter_table_cmds: + alter_table_cmd { $$ = makeList1($1); } + | alter_table_cmds ',' alter_table_cmd { $$ = lappend($1, $3); } + ; + +alter_table_cmd: + /* ALTER TABLE ADD [COLUMN] */ + ADD opt_column columnDef + { + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_AddColumn; + n->def = $3; $$ = (Node *)n; } /* ALTER TABLE ALTER [COLUMN] {SET DEFAULT |DROP DEFAULT} */ - | ALTER TABLE relation_expr ALTER opt_column ColId alter_column_default + | ALTER opt_column ColId alter_column_default { - AlterTableStmt *n = makeNode(AlterTableStmt); - n->subtype = 'T'; - n->relation = $3; - n->name = $6; - n->def = $7; + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_ColumnDefault; + n->name = $3; + n->def = $4; $$ = (Node *)n; } /* ALTER TABLE ALTER [COLUMN] DROP NOT NULL */ - | ALTER TABLE relation_expr ALTER opt_column ColId DROP NOT NULL_P + | ALTER opt_column ColId DROP NOT NULL_P { - AlterTableStmt *n = makeNode(AlterTableStmt); - n->subtype = 'N'; - n->relation = $3; - n->name = $6; + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_DropNotNull; + n->name = $3; $$ = (Node *)n; } /* ALTER TABLE ALTER [COLUMN] SET NOT NULL */ - | ALTER TABLE relation_expr ALTER opt_column ColId SET NOT NULL_P + | ALTER opt_column ColId SET NOT NULL_P { - AlterTableStmt *n = makeNode(AlterTableStmt); - n->subtype = 'n'; - n->relation = $3; - n->name = $6; + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_SetNotNull; + n->name = $3; $$ = (Node *)n; } /* ALTER TABLE ALTER [COLUMN] SET STATISTICS */ - | ALTER TABLE relation_expr ALTER opt_column ColId SET STATISTICS IntegerOnly + | ALTER opt_column ColId SET STATISTICS IntegerOnly { - AlterTableStmt *n = makeNode(AlterTableStmt); - n->subtype = 'S'; - n->relation = $3; - n->name = $6; - n->def = (Node *) $9; + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_SetStatistics; + n->name = $3; + n->def = (Node *) $6; $$ = (Node *)n; } /* ALTER TABLE ALTER [COLUMN] SET STORAGE */ - | ALTER TABLE relation_expr ALTER opt_column ColId - SET STORAGE ColId + | ALTER opt_column ColId SET STORAGE ColId { - AlterTableStmt *n = makeNode(AlterTableStmt); - n->subtype = 'M'; - n->relation = $3; - n->name = $6; - n->def = (Node *) makeString($9); + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_SetStorage; + n->name = $3; + n->def = (Node *) makeString($6); $$ = (Node *)n; } /* ALTER TABLE DROP [COLUMN] [RESTRICT|CASCADE] */ - | ALTER TABLE relation_expr DROP opt_column ColId opt_drop_behavior + | DROP opt_column ColId opt_drop_behavior { - AlterTableStmt *n = makeNode(AlterTableStmt); - n->subtype = 'D'; - n->relation = $3; - n->name = $6; - n->behavior = $7; + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_DropColumn; + n->name = $3; + n->behavior = $4; + $$ = (Node *)n; + } + /* + * ALTER TABLE ALTER [COLUMN] TYPE + * [ USING ] + */ + | ALTER opt_column ColId TYPE_P Typename alter_using + { + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_AlterColumnType; + n->name = $3; + n->def = (Node *) $5; + n->transform = $6; $$ = (Node *)n; } /* ALTER TABLE ADD CONSTRAINT ... */ - | ALTER TABLE relation_expr ADD TableConstraint + | ADD TableConstraint { - AlterTableStmt *n = makeNode(AlterTableStmt); - n->subtype = 'C'; - n->relation = $3; - n->def = $5; + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_AddConstraint; + n->def = $2; $$ = (Node *)n; } /* ALTER TABLE DROP CONSTRAINT [RESTRICT|CASCADE] */ - | ALTER TABLE relation_expr DROP CONSTRAINT name opt_drop_behavior + | DROP CONSTRAINT name opt_drop_behavior { - AlterTableStmt *n = makeNode(AlterTableStmt); - n->subtype = 'X'; - n->relation = $3; - n->name = $6; - n->behavior = $7; + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_DropConstraint; + n->name = $3; + n->behavior = $4; $$ = (Node *)n; } /* ALTER TABLE SET WITHOUT OIDS */ - | ALTER TABLE relation_expr SET WITHOUT OIDS + | SET WITHOUT OIDS { - AlterTableStmt *n = makeNode(AlterTableStmt); - n->relation = $3; - n->subtype = 'o'; + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_DropOids; $$ = (Node *)n; } - /* ALTER TABLE CREATE TOAST TABLE */ - | ALTER TABLE qualified_name CREATE TOAST TABLE + /* ALTER TABLE CREATE TOAST TABLE -- ONLY */ + | CREATE TOAST TABLE { - AlterTableStmt *n = makeNode(AlterTableStmt); - n->subtype = 'E'; - $3->inhOpt = INH_NO; - n->relation = $3; + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_ToastTable; $$ = (Node *)n; } /* ALTER TABLE OWNER TO UserId */ - | ALTER TABLE qualified_name OWNER TO UserId + | OWNER TO UserId { - AlterTableStmt *n = makeNode(AlterTableStmt); - n->subtype = 'U'; - $3->inhOpt = INH_NO; - n->relation = $3; - n->name = $6; + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_ChangeOwner; + n->name = $3; $$ = (Node *)n; } /* ALTER TABLE CLUSTER ON */ - | ALTER TABLE qualified_name CLUSTER ON name + | CLUSTER ON name { - AlterTableStmt *n = makeNode(AlterTableStmt); - n->subtype = 'L'; - n->relation = $3; - n->name = $6; + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_ClusterOn; + n->name = $3; $$ = (Node *)n; } ; @@ -1261,7 +1276,7 @@ alter_column_default: else $$ = $3; } - | DROP DEFAULT { $$ = NULL; } + | DROP DEFAULT { $$ = NULL; } ; opt_drop_behavior: @@ -1270,6 +1285,10 @@ opt_drop_behavior: | /* EMPTY */ { $$ = DROP_RESTRICT; /* default */ } ; +alter_using: + USING a_expr { $$ = $2; } + | /* EMPTY */ { $$ = NULL; } + ; /***************************************************************************** * @@ -3525,16 +3544,22 @@ RenameStmt: ALTER AGGREGATE func_name '(' aggr_argtype ')' RENAME TO name n->newname = $6; $$ = (Node *)n; } - | ALTER TABLE relation_expr RENAME opt_column opt_name TO name + | ALTER TABLE relation_expr RENAME TO name { RenameStmt *n = makeNode(RenameStmt); + n->renameType = OBJECT_TABLE; + n->relation = $3; + n->subname = NULL; + n->newname = $6; + $$ = (Node *)n; + } + | ALTER TABLE relation_expr RENAME opt_column name TO name + { + RenameStmt *n = makeNode(RenameStmt); + n->renameType = OBJECT_COLUMN; n->relation = $3; n->subname = $6; n->newname = $8; - if ($6 == NULL) - n->renameType = OBJECT_TABLE; - else - n->renameType = OBJECT_COLUMN; $$ = (Node *)n; } | ALTER TRIGGER name ON relation_expr RENAME TO name @@ -3556,10 +3581,6 @@ RenameStmt: ALTER AGGREGATE func_name '(' aggr_argtype ')' RENAME TO name } ; -opt_name: name { $$ = $1; } - | /*EMPTY*/ { $$ = NULL; } - ; - opt_column: COLUMN { $$ = COLUMN; } | /*EMPTY*/ { $$ = 0; } ; diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 98c63fd0e6..68e9f84672 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.213 2004/04/22 02:58:20 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.214 2004/05/05 04:48:46 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -43,7 +43,6 @@ #include "commands/view.h" #include "miscadmin.h" #include "nodes/makefuncs.h" -#include "parser/parse_clause.h" #include "parser/parse_expr.h" #include "parser/parse_type.h" #include "rewrite/rewriteDefine.h" @@ -497,126 +496,8 @@ ProcessUtility(Node *parsetree, ExecRenameStmt((RenameStmt *) parsetree); break; - /* various Alter Table forms */ - case T_AlterTableStmt: - { - AlterTableStmt *stmt = (AlterTableStmt *) parsetree; - Oid relid; - - relid = RangeVarGetRelid(stmt->relation, false); - - /* - * Some or all of these functions are recursive to cover - * inherited things, so permission checks are done there. - */ - switch (stmt->subtype) - { - case 'A': /* ADD COLUMN */ - - /* - * Recursively add column to table and, if - * requested, to descendants - */ - AlterTableAddColumn(relid, - interpretInhOption(stmt->relation->inhOpt), - (ColumnDef *) stmt->def); - break; - case 'T': /* ALTER COLUMN DEFAULT */ - - /* - * Recursively alter column default for table and, - * if requested, for descendants - */ - AlterTableAlterColumnDefault(relid, - interpretInhOption(stmt->relation->inhOpt), - stmt->name, - stmt->def); - break; - case 'N': /* ALTER COLUMN DROP NOT NULL */ - AlterTableAlterColumnDropNotNull(relid, - interpretInhOption(stmt->relation->inhOpt), - stmt->name); - break; - case 'n': /* ALTER COLUMN SET NOT NULL */ - AlterTableAlterColumnSetNotNull(relid, - interpretInhOption(stmt->relation->inhOpt), - stmt->name); - break; - case 'S': /* ALTER COLUMN STATISTICS */ - case 'M': /* ALTER COLUMN STORAGE */ - - /* - * Recursively alter column statistics for table - * and, if requested, for descendants - */ - AlterTableAlterColumnFlags(relid, - interpretInhOption(stmt->relation->inhOpt), - stmt->name, - stmt->def, - &(stmt->subtype)); - break; - case 'D': /* DROP COLUMN */ - - /* - * Recursively drop column from table and, if - * requested, from descendants - */ - AlterTableDropColumn(relid, - interpretInhOption(stmt->relation->inhOpt), - false, - stmt->name, - stmt->behavior); - break; - case 'C': /* ADD CONSTRAINT */ - - /* - * Recursively add constraint to table and, if - * requested, to descendants - */ - AlterTableAddConstraint(relid, - interpretInhOption(stmt->relation->inhOpt), - (List *) stmt->def); - break; - case 'X': /* DROP CONSTRAINT */ - - /* - * Recursively drop constraint from table and, if - * requested, from descendants - */ - AlterTableDropConstraint(relid, - interpretInhOption(stmt->relation->inhOpt), - stmt->name, - stmt->behavior); - break; - case 'E': /* CREATE TOAST TABLE */ - AlterTableCreateToastTable(relid, false); - break; - case 'U': /* ALTER OWNER */ - /* check that we are the superuser */ - if (!superuser()) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to alter owner"))); - /* get_usesysid raises an error if no such user */ - AlterTableOwner(relid, - get_usesysid(stmt->name)); - break; - case 'L': /* CLUSTER ON */ - AlterTableClusterOn(relid, stmt->name); - break; - case 'o': /* SET WITHOUT OIDS */ - AlterTableAlterOids(relid, - false, - interpretInhOption(stmt->relation->inhOpt), - DROP_RESTRICT); - break; - default: /* oops */ - elog(ERROR, "unrecognized alter table type: %d", - (int) stmt->subtype); - break; - } - } + AlterTable((AlterTableStmt *) parsetree); break; case T_AlterDomainStmt: @@ -736,11 +617,15 @@ ProcessUtility(Node *parsetree, stmt->idxname, /* index name */ stmt->accessMethod, /* am name */ stmt->indexParams, /* parameters */ + (Expr *) stmt->whereClause, + stmt->rangetable, stmt->unique, stmt->primary, stmt->isconstraint, - (Expr *) stmt->whereClause, - stmt->rangetable); + false, /* is_alter_table */ + true, /* check_rights */ + false, /* skip_build */ + false); /* quiet */ } break; diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 3960152f38..412aa252d6 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -3,7 +3,7 @@ * back to source text * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.163 2004/03/17 20:48:42 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.164 2004/05/05 04:48:46 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -149,15 +149,16 @@ static char *query_getviewrule = "SELECT * FROM pg_catalog.pg_rewrite WHERE ev_c static char *deparse_expression_pretty(Node *expr, List *dpcontext, bool forceprefix, bool showimplicit, int prettyFlags, int startIndent); -static text *pg_do_getviewdef(Oid viewoid, int prettyFlags); +static char *pg_get_viewdef_worker(Oid viewoid, int prettyFlags); static void decompile_column_index_array(Datum column_index_array, Oid relId, StringInfo buf); -static Datum pg_get_ruledef_worker(Oid ruleoid, int prettyFlags); -static Datum pg_get_indexdef_worker(Oid indexrelid, int colno, - int prettyFlags); -static Datum pg_get_constraintdef_worker(Oid constraintId, int prettyFlags); -static Datum pg_get_expr_worker(text *expr, Oid relid, char *relname, - int prettyFlags); +static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags); +static char *pg_get_indexdef_worker(Oid indexrelid, int colno, + int prettyFlags); +static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand, + int prettyFlags); +static char *pg_get_expr_worker(text *expr, Oid relid, char *relname, + int prettyFlags); static void make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc, int prettyFlags); static void make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc, @@ -208,6 +209,7 @@ static char *generate_relation_name(Oid relid); static char *generate_function_name(Oid funcid, int nargs, Oid *argtypes); static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2); static void print_operator_name(StringInfo buf, List *opname); +static text *string_to_text(char *str); #define only_marker(rte) ((rte)->inh ? "" : "ONLY ") @@ -223,7 +225,7 @@ pg_get_ruledef(PG_FUNCTION_ARGS) { Oid ruleoid = PG_GETARG_OID(0); - return pg_get_ruledef_worker(ruleoid, 0); + PG_RETURN_TEXT_P(string_to_text(pg_get_ruledef_worker(ruleoid, 0))); } @@ -235,21 +237,24 @@ pg_get_ruledef_ext(PG_FUNCTION_ARGS) int prettyFlags; prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0; - return pg_get_ruledef_worker(ruleoid, prettyFlags); + PG_RETURN_TEXT_P(string_to_text(pg_get_ruledef_worker(ruleoid, prettyFlags))); } -static Datum +static char * pg_get_ruledef_worker(Oid ruleoid, int prettyFlags) { - text *ruledef; Datum args[1]; char nulls[1]; int spirc; HeapTuple ruletup; TupleDesc rulettc; StringInfoData buf; - int len; + + /* + * Do this first so that string is alloc'd in outer context not SPI's. + */ + initStringInfo(&buf); /* * Connect to SPI manager @@ -283,39 +288,24 @@ pg_get_ruledef_worker(Oid ruleoid, int prettyFlags) if (spirc != SPI_OK_SELECT) elog(ERROR, "failed to get pg_rewrite tuple for rule %u", ruleoid); if (SPI_processed != 1) + appendStringInfo(&buf, "-"); + else { - if (SPI_finish() != SPI_OK_FINISH) - elog(ERROR, "SPI_finish failed"); - ruledef = palloc(VARHDRSZ + 1); - VARATT_SIZEP(ruledef) = VARHDRSZ + 1; - VARDATA(ruledef)[0] = '-'; - PG_RETURN_TEXT_P(ruledef); + /* + * Get the rules definition and put it into executors memory + */ + ruletup = SPI_tuptable->vals[0]; + rulettc = SPI_tuptable->tupdesc; + make_ruledef(&buf, ruletup, rulettc, prettyFlags); } - ruletup = SPI_tuptable->vals[0]; - rulettc = SPI_tuptable->tupdesc; - - /* - * Get the rules definition and put it into executors memory - */ - initStringInfo(&buf); - make_ruledef(&buf, ruletup, rulettc, prettyFlags); - len = buf.len + VARHDRSZ; - ruledef = SPI_palloc(len); - VARATT_SIZEP(ruledef) = len; - memcpy(VARDATA(ruledef), buf.data, buf.len); - pfree(buf.data); - /* * Disconnect from SPI manager */ if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); - /* - * Easy - isn't it? - */ - PG_RETURN_TEXT_P(ruledef); + return buf.data; } @@ -329,10 +319,8 @@ pg_get_viewdef(PG_FUNCTION_ARGS) { /* By OID */ Oid viewoid = PG_GETARG_OID(0); - text *ruledef; - ruledef = pg_do_getviewdef(viewoid, 0); - PG_RETURN_TEXT_P(ruledef); + PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, 0))); } @@ -342,12 +330,10 @@ pg_get_viewdef_ext(PG_FUNCTION_ARGS) /* By OID */ Oid viewoid = PG_GETARG_OID(0); bool pretty = PG_GETARG_BOOL(1); - text *ruledef; int prettyFlags; prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0; - ruledef = pg_do_getviewdef(viewoid, prettyFlags); - PG_RETURN_TEXT_P(ruledef); + PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, prettyFlags))); } Datum @@ -357,14 +343,12 @@ pg_get_viewdef_name(PG_FUNCTION_ARGS) text *viewname = PG_GETARG_TEXT_P(0); RangeVar *viewrel; Oid viewoid; - text *ruledef; viewrel = makeRangeVarFromNameList(textToQualifiedNameList(viewname, "get_viewdef")); viewoid = RangeVarGetRelid(viewrel, false); - ruledef = pg_do_getviewdef(viewoid, 0); - PG_RETURN_TEXT_P(ruledef); + PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, 0))); } @@ -377,31 +361,32 @@ pg_get_viewdef_name_ext(PG_FUNCTION_ARGS) int prettyFlags; RangeVar *viewrel; Oid viewoid; - text *ruledef; prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0; viewrel = makeRangeVarFromNameList(textToQualifiedNameList(viewname, "get_viewdef")); viewoid = RangeVarGetRelid(viewrel, false); - ruledef = pg_do_getviewdef(viewoid, prettyFlags); - PG_RETURN_TEXT_P(ruledef); + PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, prettyFlags))); } /* * Common code for by-OID and by-name variants of pg_get_viewdef */ -static text * -pg_do_getviewdef(Oid viewoid, int prettyFlags) +static char * +pg_get_viewdef_worker(Oid viewoid, int prettyFlags) { - text *ruledef; Datum args[2]; char nulls[2]; int spirc; HeapTuple ruletup; TupleDesc rulettc; StringInfoData buf; - int len; + + /* + * Do this first so that string is alloc'd in outer context not SPI's. + */ + initStringInfo(&buf); /* * Connect to SPI manager @@ -437,7 +422,6 @@ pg_do_getviewdef(Oid viewoid, int prettyFlags) spirc = SPI_execp(plan_getviewrule, args, nulls, 2); if (spirc != SPI_OK_SELECT) elog(ERROR, "failed to get pg_rewrite tuple for view %u", viewoid); - initStringInfo(&buf); if (SPI_processed != 1) appendStringInfo(&buf, "Not a view"); else @@ -449,11 +433,6 @@ pg_do_getviewdef(Oid viewoid, int prettyFlags) rulettc = SPI_tuptable->tupdesc; make_viewdef(&buf, ruletup, rulettc, prettyFlags); } - len = buf.len + VARHDRSZ; - ruledef = SPI_palloc(len); - VARATT_SIZEP(ruledef) = len; - memcpy(VARDATA(ruledef), buf.data, buf.len); - pfree(buf.data); /* * Disconnect from SPI manager @@ -461,7 +440,7 @@ pg_do_getviewdef(Oid viewoid, int prettyFlags) if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); - return ruledef; + return buf.data; } /* ---------- @@ -472,10 +451,8 @@ Datum pg_get_triggerdef(PG_FUNCTION_ARGS) { Oid trigid = PG_GETARG_OID(0); - text *trigdef; HeapTuple ht_trig; Form_pg_trigger trigrec; - int len; StringInfoData buf; Relation tgrel; ScanKeyData skey[1]; @@ -597,21 +574,12 @@ pg_get_triggerdef(PG_FUNCTION_ARGS) /* We deliberately do not put semi-colon at end */ appendStringInfo(&buf, ")"); - /* - * Create the result as a TEXT datum, and free working data - */ - len = buf.len + VARHDRSZ; - trigdef = (text *) palloc(len); - VARATT_SIZEP(trigdef) = len; - memcpy(VARDATA(trigdef), buf.data, buf.len); - - pfree(buf.data); - + /* Clean up */ systable_endscan(tgscan); heap_close(tgrel, AccessShareLock); - PG_RETURN_TEXT_P(trigdef); + PG_RETURN_TEXT_P(string_to_text(buf.data)); } /* ---------- @@ -627,7 +595,7 @@ pg_get_indexdef(PG_FUNCTION_ARGS) { Oid indexrelid = PG_GETARG_OID(0); - return pg_get_indexdef_worker(indexrelid, 0, 0); + PG_RETURN_TEXT_P(string_to_text(pg_get_indexdef_worker(indexrelid, 0, 0))); } Datum @@ -639,13 +607,19 @@ pg_get_indexdef_ext(PG_FUNCTION_ARGS) int prettyFlags; prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0; - return pg_get_indexdef_worker(indexrelid, colno, prettyFlags); + PG_RETURN_TEXT_P(string_to_text(pg_get_indexdef_worker(indexrelid, colno, prettyFlags))); } -static Datum +/* Internal version that returns a palloc'd C string */ +char * +pg_get_indexdef_string(Oid indexrelid) +{ + return pg_get_indexdef_worker(indexrelid, 0, 0); +} + +static char * pg_get_indexdef_worker(Oid indexrelid, int colno, int prettyFlags) { - text *indexdef; HeapTuple ht_idx; HeapTuple ht_idxrel; HeapTuple ht_am; @@ -655,7 +629,6 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, int prettyFlags) List *indexprs; List *context; Oid indrelid; - int len; int keyno; Oid keycoltype; StringInfoData buf; @@ -817,21 +790,12 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, int prettyFlags) } } - /* - * Create the result as a TEXT datum, and free working data - */ - len = buf.len + VARHDRSZ; - indexdef = (text *) palloc(len); - VARATT_SIZEP(indexdef) = len; - memcpy(VARDATA(indexdef), buf.data, buf.len); - - pfree(buf.data); - + /* Clean up */ ReleaseSysCache(ht_idx); ReleaseSysCache(ht_idxrel); ReleaseSysCache(ht_am); - PG_RETURN_TEXT_P(indexdef); + return buf.data; } @@ -846,7 +810,8 @@ pg_get_constraintdef(PG_FUNCTION_ARGS) { Oid constraintId = PG_GETARG_OID(0); - return pg_get_constraintdef_worker(constraintId, 0); + PG_RETURN_TEXT_P(string_to_text(pg_get_constraintdef_worker(constraintId, + false, 0))); } Datum @@ -857,16 +822,22 @@ pg_get_constraintdef_ext(PG_FUNCTION_ARGS) int prettyFlags; prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0; - return pg_get_constraintdef_worker(constraintId, prettyFlags); + PG_RETURN_TEXT_P(string_to_text(pg_get_constraintdef_worker(constraintId, + false, prettyFlags))); } - -static Datum -pg_get_constraintdef_worker(Oid constraintId, int prettyFlags) +/* Internal version that returns a palloc'd C string */ +char * +pg_get_constraintdef_string(Oid constraintId) +{ + return pg_get_constraintdef_worker(constraintId, true, 0); +} + +static char * +pg_get_constraintdef_worker(Oid constraintId, bool fullCommand, + int prettyFlags) { - text *result; StringInfoData buf; - int len; Relation conDesc; SysScanDesc conscan; ScanKeyData skey[1]; @@ -894,6 +865,13 @@ pg_get_constraintdef_worker(Oid constraintId, int prettyFlags) initStringInfo(&buf); + if (fullCommand && OidIsValid(conForm->conrelid)) + { + appendStringInfo(&buf, "ALTER TABLE ONLY %s ADD CONSTRAINT %s ", + generate_relation_name(conForm->conrelid), + quote_identifier(NameStr(conForm->conname))); + } + switch (conForm->contype) { case CONSTRAINT_FOREIGN: @@ -1087,18 +1065,11 @@ pg_get_constraintdef_worker(Oid constraintId, int prettyFlags) break; } - /* Record the results */ - len = buf.len + VARHDRSZ; - result = (text *) palloc(len); - VARATT_SIZEP(result) = len; - memcpy(VARDATA(result), buf.data, buf.len); - /* Cleanup */ - pfree(buf.data); systable_endscan(conscan); heap_close(conDesc, AccessShareLock); - PG_RETURN_TEXT_P(result); + return buf.data; } @@ -1157,7 +1128,7 @@ pg_get_expr(PG_FUNCTION_ARGS) if (relname == NULL) PG_RETURN_NULL(); /* should we raise an error? */ - return pg_get_expr_worker(expr, relid, relname, 0); + PG_RETURN_TEXT_P(string_to_text(pg_get_expr_worker(expr, relid, relname, 0))); } Datum @@ -1176,13 +1147,12 @@ pg_get_expr_ext(PG_FUNCTION_ARGS) if (relname == NULL) PG_RETURN_NULL(); /* should we raise an error? */ - return pg_get_expr_worker(expr, relid, relname, prettyFlags); + PG_RETURN_TEXT_P(string_to_text(pg_get_expr_worker(expr, relid, relname, prettyFlags))); } -static Datum +static char * pg_get_expr_worker(text *expr, Oid relid, char *relname, int prettyFlags) { - text *result; Node *node; List *context; char *exprstr; @@ -1200,11 +1170,7 @@ pg_get_expr_worker(text *expr, Oid relid, char *relname, int prettyFlags) str = deparse_expression_pretty(node, context, false, false, prettyFlags, 0); - /* Pass the result back as TEXT */ - result = DatumGetTextP(DirectFunctionCall1(textin, - CStringGetDatum(str))); - - PG_RETURN_TEXT_P(result); + return str; } @@ -4364,3 +4330,25 @@ print_operator_name(StringInfo buf, List *opname) appendStringInfo(buf, "%s)", strVal(lfirst(opname))); } } + +/* + * Given a C string, produce a TEXT datum. + * + * We assume that the input was palloc'd and may be freed. + */ +static text * +string_to_text(char *str) +{ + text *result; + int slen = strlen(str); + int tlen; + + tlen = slen + VARHDRSZ; + result = (text *) palloc(tlen); + VARATT_SIZEP(result) = tlen; + memcpy(VARDATA(result), str, slen); + + pfree(str); + + return result; +} diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index 583851c749..0e230d7e86 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/dependency.h,v 1.11 2003/11/29 22:40:58 pgsql Exp $ + * $PostgreSQL: pgsql/src/include/catalog/dependency.h,v 1.12 2004/05/05 04:48:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -17,7 +17,7 @@ #include "nodes/parsenodes.h" /* for DropBehavior */ -/* +/*---------- * Precise semantics of a dependency relationship are specified by the * DependencyType code (which is stored in a "char" field in pg_depend, * so we assign ASCII-code values to the enumeration members). @@ -56,6 +56,7 @@ * contain zeroes. * * Other dependency flavors may be needed in future. + *---------- */ typedef enum DependencyType @@ -79,6 +80,28 @@ typedef struct ObjectAddress } ObjectAddress; +/* + * This enum covers all system catalogs whose OIDs can appear in classId. + */ +typedef enum ObjectClass +{ + OCLASS_CLASS, /* pg_class */ + OCLASS_PROC, /* pg_proc */ + OCLASS_TYPE, /* pg_type */ + OCLASS_CAST, /* pg_cast */ + OCLASS_CONSTRAINT, /* pg_constraint */ + OCLASS_CONVERSION, /* pg_conversion */ + OCLASS_DEFAULT, /* pg_attrdef */ + OCLASS_LANGUAGE, /* pg_language */ + OCLASS_OPERATOR, /* pg_operator */ + OCLASS_OPCLASS, /* pg_opclass */ + OCLASS_REWRITE, /* pg_rewrite */ + OCLASS_TRIGGER, /* pg_trigger */ + OCLASS_SCHEMA, /* pg_namespace */ + MAX_OCLASS /* MUST BE LAST */ +} ObjectClass; + + /* in dependency.c */ extern void performDeletion(const ObjectAddress *object, @@ -96,6 +119,10 @@ extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender, DependencyType behavior, DependencyType self_behavior); +extern ObjectClass getObjectClass(const ObjectAddress *object); + +extern char *getObjectDescription(const ObjectAddress *object); + /* in pg_depend.c */ extern void recordDependencyOn(const ObjectAddress *depender, diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index f635ef1bd0..2913d53e52 100644 --- a/src/include/catalog/heap.h +++ b/src/include/catalog/heap.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/heap.h,v 1.65 2004/03/23 19:35:17 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/heap.h,v 1.66 2004/05/05 04:48:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -27,6 +27,14 @@ typedef struct RawColumnDefault * tree) */ } RawColumnDefault; +typedef struct CookedConstraint +{ + ConstrType contype; /* CONSTR_DEFAULT or CONSTR_CHECK */ + char *name; /* name, or NULL if none */ + AttrNumber attnum; /* which attr (only for DEFAULT) */ + Node *expr; /* transformed default or check expr */ +} CookedConstraint; + extern Relation heap_create(const char *relname, Oid relnamespace, TupleDesc tupDesc, @@ -52,10 +60,12 @@ extern void heap_truncate(Oid rid); extern void heap_truncate_check_FKs(Relation rel); -extern void AddRelationRawConstraints(Relation rel, +extern List *AddRelationRawConstraints(Relation rel, List *rawColDefaults, List *rawConstraints); +extern void StoreAttrDefault(Relation rel, AttrNumber attnum, char *adbin); + extern Node *cookDefault(ParseState *pstate, Node *raw_default, Oid atttypid, diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h index da210c483a..13c367c902 100644 --- a/src/include/catalog/index.h +++ b/src/include/catalog/index.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/index.h,v 1.54 2003/11/29 22:40:58 pgsql Exp $ + * $PostgreSQL: pgsql/src/include/catalog/index.h,v 1.55 2004/05/05 04:48:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -37,7 +37,8 @@ extern Oid index_create(Oid heapRelationId, Oid *classObjectId, bool primary, bool isconstraint, - bool allow_system_table_mods); + bool allow_system_table_mods, + bool skip_build); extern void index_drop(Oid indexId); diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h index 72295803ab..5474fdd549 100644 --- a/src/include/commands/cluster.h +++ b/src/include/commands/cluster.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994-5, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/commands/cluster.h,v 1.20 2003/11/29 22:40:59 pgsql Exp $ + * $PostgreSQL: pgsql/src/include/commands/cluster.h,v 1.21 2004/05/05 04:48:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -20,5 +20,9 @@ extern void cluster(ClusterStmt *stmt); extern void rebuild_relation(Relation OldHeap, Oid indexOid); +extern Oid make_new_heap(Oid OIDOldHeap, const char *NewName); +extern List *get_indexattr_list(Relation OldHeap, Oid OldIndex); +extern void rebuild_indexes(Oid OIDOldHeap, List *indexes); +extern void swap_relfilenodes(Oid r1, Oid r2); #endif /* CLUSTER_H */ diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 00f5fa1a48..78fe4ab907 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/commands/defrem.h,v 1.54 2004/02/21 00:34:53 tgl Exp $ + * $PostgreSQL: pgsql/src/include/commands/defrem.h,v 1.55 2004/05/05 04:48:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -22,11 +22,15 @@ extern void DefineIndex(RangeVar *heapRelation, char *indexRelationName, char *accessMethodName, List *attributeList, + Expr *predicate, + List *rangetable, bool unique, bool primary, bool isconstraint, - Expr *predicate, - List *rangetable); + bool is_alter_table, + bool check_rights, + bool skip_build, + bool quiet); extern void RemoveIndex(RangeVar *relation, DropBehavior behavior); extern void ReindexIndex(RangeVar *indexRelation, bool force); extern void ReindexTable(RangeVar *relation, bool force); diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h index 889b4dfa62..f9f03c1bd0 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/commands/tablecmds.h,v 1.15 2004/03/23 19:35:17 tgl Exp $ + * $PostgreSQL: pgsql/src/include/commands/tablecmds.h,v 1.16 2004/05/05 04:48:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -16,46 +16,17 @@ #include "nodes/parsenodes.h" -extern void AlterTableAddColumn(Oid myrelid, bool recurse, ColumnDef *colDef); - -extern void AlterTableAlterColumnDropNotNull(Oid myrelid, bool recurse, - const char *colName); - -extern void AlterTableAlterColumnSetNotNull(Oid myrelid, bool recurse, - const char *colName); - -extern void AlterTableAlterColumnDefault(Oid myrelid, bool recurse, - const char *colName, - Node *newDefault); - -extern void AlterTableAlterColumnFlags(Oid myrelid, bool recurse, - const char *colName, - Node *flagValue, const char *flagType); - -extern void AlterTableDropColumn(Oid myrelid, bool recurse, bool recursing, - const char *colName, - DropBehavior behavior); - -extern void AlterTableAddConstraint(Oid myrelid, bool recurse, - List *newConstraints); - -extern void AlterTableDropConstraint(Oid myrelid, bool recurse, - const char *constrName, - DropBehavior behavior); - -extern void AlterTableClusterOn(Oid relOid, const char *indexName); - -extern void AlterTableCreateToastTable(Oid relOid, bool silent); - -extern void AlterTableOwner(Oid relationOid, int32 newOwnerSysId); - -extern void AlterTableAlterOids(Oid myrelid, bool setOid, bool recurse, - DropBehavior behavior); extern Oid DefineRelation(CreateStmt *stmt, char relkind); extern void RemoveRelation(const RangeVar *relation, DropBehavior behavior); +extern void AlterTable(AlterTableStmt *stmt); + +extern void AlterTableInternal(Oid relid, List *cmds, bool recurse); + +extern void AlterTableCreateToastTable(Oid relOid, bool silent); + extern void TruncateRelation(const RangeVar *relation); extern void renameatt(Oid myrelid, diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index d5d4f0832a..a776607ec6 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.152 2004/04/01 21:28:46 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.153 2004/05/05 04:48:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -200,6 +200,7 @@ typedef enum NodeTag T_UpdateStmt, T_SelectStmt, T_AlterTableStmt, + T_AlterTableCmd, T_AlterDomainStmt, T_SetOperationStmt, T_GrantStmt, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index ab505939bc..8eceb6e59c 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.254 2004/03/11 01:47:41 ishii Exp $ + * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.255 2004/05/05 04:48:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -762,44 +762,58 @@ typedef enum DropBehavior /* ---------------------- * Alter Table - * - * The fields are used in different ways by the different variants of - * this command. * ---------------------- */ typedef struct AlterTableStmt { NodeTag type; - char subtype; /*------------ - * A = add column - * T = alter column default - * N = alter column drop not null - * n = alter column set not null - * S = alter column statistics - * M = alter column storage - * D = drop column - * C = add constraint - * c = pre-processed add constraint - * (local in parser/analyze.c) - * X = drop constraint - * E = create toast table - * U = change owner - * L = CLUSTER ON - * o = DROP OIDS - *------------ - */ RangeVar *relation; /* table to work on */ + List *cmds; /* list of subcommands */ +} AlterTableStmt; + +typedef enum AlterTableType +{ + AT_AddColumn, /* add column */ + AT_ColumnDefault, /* alter column default */ + AT_DropNotNull, /* alter column drop not null */ + AT_SetNotNull, /* alter column set not null */ + AT_SetStatistics, /* alter column statistics */ + AT_SetStorage, /* alter column storage */ + AT_DropColumn, /* drop column */ + AT_DropColumnRecurse, /* internal to commands/tablecmds.c */ + AT_AddIndex, /* add index */ + AT_ReAddIndex, /* internal to commands/tablecmds.c */ + AT_AddConstraint, /* add constraint */ + AT_ProcessedConstraint, /* pre-processed add constraint + * (local in parser/analyze.c) */ + AT_DropConstraint, /* drop constraint */ + AT_DropConstraintQuietly, /* drop constraint, no error/warning + * (local in commands/tablecmds.c) */ + AT_AlterColumnType, /* alter column type */ + AT_ToastTable, /* create toast table */ + AT_ChangeOwner, /* change owner */ + AT_ClusterOn, /* CLUSTER ON */ + AT_DropOids /* SET WITHOUT OIDS */ +} AlterTableType; + +typedef struct AlterTableCmd /* one subcommand of an ALTER TABLE */ +{ + NodeTag type; + AlterTableType subtype; /* Type of table alteration to apply */ char *name; /* column or constraint name to act on, or * new owner */ - Node *def; /* definition of new column or constraint */ + Node *def; /* definition of new column, column type, + * index, or constraint */ + Node *transform; /* transformation expr for ALTER TYPE */ DropBehavior behavior; /* RESTRICT or CASCADE for DROP cases */ -} AlterTableStmt; +} AlterTableCmd; + /* ---------------------- * Alter Domain * * The fields are used in different ways by the different variants of - * this command. Subtypes should match AlterTable subtypes where possible. + * this command. * ---------------------- */ typedef struct AlterDomainStmt @@ -814,7 +828,7 @@ typedef struct AlterDomainStmt * U = change owner *------------ */ - List *typename; /* table to work on */ + List *typename; /* domain to work on */ char *name; /* column or constraint name to act on, or * new owner */ Node *def; /* definition of default or constraint */ @@ -922,6 +936,8 @@ typedef struct CreateStmt * Definitions for plain (non-FOREIGN KEY) constraints in CreateStmt * * XXX probably these ought to be unified with FkConstraints at some point? + * To this end we include CONSTR_FOREIGN in the ConstrType enum, even though + * the parser does not generate it. * * For constraints that use expressions (CONSTR_DEFAULT, CONSTR_CHECK) * we may have the expression in either "raw" form (an untransformed @@ -944,6 +960,7 @@ typedef enum ConstrType /* types of constraints */ CONSTR_NOTNULL, CONSTR_DEFAULT, CONSTR_CHECK, + CONSTR_FOREIGN, CONSTR_PRIMARY, CONSTR_UNIQUE, CONSTR_ATTR_DEFERRABLE, /* attributes for previous constraint node */ @@ -1291,7 +1308,7 @@ typedef struct FetchStmt typedef struct IndexStmt { NodeTag type; - char *idxname; /* name of the index */ + char *idxname; /* name of new index, or NULL for default */ RangeVar *relation; /* relation to build index on */ char *accessMethod; /* name of access method (eg. btree) */ List *indexParams; /* a list of IndexElem */ diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h index e3cf88b4fd..76c77025ec 100644 --- a/src/include/parser/analyze.h +++ b/src/include/parser/analyze.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/parser/analyze.h,v 1.25 2004/01/23 02:13:12 neilc Exp $ + * $PostgreSQL: pgsql/src/include/parser/analyze.h,v 1.26 2004/05/05 04:48:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -22,6 +22,9 @@ extern List *parse_analyze_varparams(Node *parseTree, Oid **paramTypes, extern List *parse_sub_analyze(Node *parseTree, ParseState *parentParseState); extern List *analyzeCreateSchemaStmt(CreateSchemaStmt *stmt); +extern char *makeObjectName(const char *name1, const char *name2, + const char *typename); + extern void CheckSelectForUpdate(Query *qry); #endif /* ANALYZE_H */ diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 02e6cbca49..59fb0a9a85 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/builtins.h,v 1.236 2004/04/01 21:28:46 tgl Exp $ + * $PostgreSQL: pgsql/src/include/utils/builtins.h,v 1.237 2004/05/05 04:48:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -463,9 +463,11 @@ extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS); extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS); extern Datum pg_get_indexdef(PG_FUNCTION_ARGS); extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS); +extern char *pg_get_indexdef_string(Oid indexrelid); extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS); extern Datum pg_get_constraintdef(PG_FUNCTION_ARGS); extern Datum pg_get_constraintdef_ext(PG_FUNCTION_ARGS); +extern char *pg_get_constraintdef_string(Oid constraintId); extern Datum pg_get_userbyid(PG_FUNCTION_ARGS); extern Datum pg_get_expr(PG_FUNCTION_ARGS); extern Datum pg_get_expr_ext(PG_FUNCTION_ARGS); diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index 089651c727..feb0852032 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -7,7 +7,7 @@ COMMENT ON TABLE tmp_wrong IS 'table comment'; ERROR: relation "tmp_wrong" does not exist COMMENT ON TABLE tmp IS 'table comment'; COMMENT ON TABLE tmp IS NULL; -ALTER TABLE tmp ADD COLUMN a int4; +ALTER TABLE tmp ADD COLUMN a int4 default 3; ALTER TABLE tmp ADD COLUMN b name; ALTER TABLE tmp ADD COLUMN c text; ALTER TABLE tmp ADD COLUMN d float8; @@ -419,7 +419,6 @@ create table atacc1 ( test int ); insert into atacc1 (test) values (NULL); -- add a primary key (fails) alter table atacc1 add constraint atacc_test1 primary key (test); -NOTICE: ALTER TABLE / ADD PRIMARY KEY will create implicit index "atacc_test1" for table "atacc1" ERROR: column "test" contains null values insert into atacc1 (test) values (3); drop table atacc1; @@ -1143,3 +1142,96 @@ select f3,max(f1) from foo group by f3; zz | qq (1 row) +-- Simple tests for alter table column type +alter table foo alter f1 TYPE integer; -- fails +ERROR: column "f1" cannot be cast to type "pg_catalog.int4" +alter table foo alter f1 TYPE varchar(10); +create table anothertab (atcol1 serial8, atcol2 boolean, + constraint anothertab_chk check (atcol1 <= 3)); +NOTICE: CREATE TABLE will create implicit sequence "anothertab_atcol1_seq" for "serial" column "anothertab.atcol1" +insert into anothertab (atcol1, atcol2) values (default, true); +insert into anothertab (atcol1, atcol2) values (default, false); +select * from anothertab; + atcol1 | atcol2 +--------+-------- + 1 | t + 2 | f +(2 rows) + +alter table anothertab alter column atcol1 type boolean; -- fails +ERROR: column "atcol1" cannot be cast to type "pg_catalog.bool" +alter table anothertab alter column atcol1 type integer; +select * from anothertab; + atcol1 | atcol2 +--------+-------- + 1 | t + 2 | f +(2 rows) + +insert into anothertab (atcol1, atcol2) values (45, null); -- fails +ERROR: new row for relation "anothertab" violates check constraint "anothertab_chk" +insert into anothertab (atcol1, atcol2) values (default, null); +select * from anothertab; + atcol1 | atcol2 +--------+-------- + 1 | t + 2 | f + 3 | +(3 rows) + +alter table anothertab alter column atcol2 type text + using case when atcol2 is true then 'IT WAS TRUE' + when atcol2 is false then 'IT WAS FALSE' + else 'IT WAS NULL!' end; +select * from anothertab; + atcol1 | atcol2 +--------+-------------- + 1 | IT WAS TRUE + 2 | IT WAS FALSE + 3 | IT WAS NULL! +(3 rows) + +alter table anothertab alter column atcol1 type boolean + using case when atcol1 % 2 = 0 then true else false end; -- fails +ERROR: default for column "atcol1" cannot be cast to type "pg_catalog.bool" +alter table anothertab alter column atcol1 drop default; +alter table anothertab alter column atcol1 type boolean + using case when atcol1 % 2 = 0 then true else false end; -- fails +ERROR: operator does not exist: boolean <= integer +HINT: No operator matches the given name and argument type(s). You may need to add explicit type casts. +alter table anothertab drop constraint anothertab_chk; +alter table anothertab alter column atcol1 type boolean + using case when atcol1 % 2 = 0 then true else false end; +select * from anothertab; + atcol1 | atcol2 +--------+-------------- + f | IT WAS TRUE + t | IT WAS FALSE + f | IT WAS NULL! +(3 rows) + +drop table anothertab; +create table another (f1 int, f2 text); +insert into another values(1, 'one'); +insert into another values(2, 'two'); +insert into another values(3, 'three'); +select * from another; + f1 | f2 +----+------- + 1 | one + 2 | two + 3 | three +(3 rows) + +alter table another + alter f1 type text using f2 || ' more', + alter f2 type bigint using f1 * 10; +select * from another; + f1 | f2 +------------+---- + one more | 10 + two more | 20 + three more | 30 +(3 rows) + +drop table another; diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out index bd4bccfc9d..5aae3720d3 100644 --- a/src/test/regress/expected/foreign_key.out +++ b/src/test/regress/expected/foreign_key.out @@ -145,6 +145,28 @@ SELECT * FROM FKTABLE; | | 8 (5 rows) +-- Try altering the column type where foreign keys are involved +ALTER TABLE PKTABLE ALTER COLUMN ptest1 TYPE bigint; +ALTER TABLE FKTABLE ALTER COLUMN ftest1 TYPE bigint; +SELECT * FROM PKTABLE; + ptest1 | ptest2 | ptest3 +--------+--------+--------- + 1 | 3 | Test1-2 + 3 | 6 | Test3 + 4 | 8 | Test4 + 1 | 4 | Test2 +(4 rows) + +SELECT * FROM FKTABLE; + ftest1 | ftest2 | ftest3 +--------+--------+-------- + 1 | 3 | 5 + 3 | 6 | 12 + | | 0 + | | 4 + | | 8 +(5 rows) + DROP TABLE PKTABLE CASCADE; NOTICE: drop cascades to constraint constrname on table fktable DROP TABLE FKTABLE; diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out index f1890595ea..e1205a9b00 100644 --- a/src/test/regress/expected/inherit.out +++ b/src/test/regress/expected/inherit.out @@ -613,3 +613,12 @@ SELECT * FROM inhf; /* Single entry with value 'text' */ text (1 row) +-- Test changing the type of inherited columns +insert into d values('test','one','two','three'); +alter table a alter column aa type integer using bit_length(aa); +select * from d; + aa | bb | cc | dd +----+-----+-----+------- + 32 | one | two | three +(1 row) + diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index dcd968a0b6..6077788583 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -9,7 +9,7 @@ COMMENT ON TABLE tmp_wrong IS 'table comment'; COMMENT ON TABLE tmp IS 'table comment'; COMMENT ON TABLE tmp IS NULL; -ALTER TABLE tmp ADD COLUMN a int4; +ALTER TABLE tmp ADD COLUMN a int4 default 3; ALTER TABLE tmp ADD COLUMN b name; @@ -918,3 +918,60 @@ select * from foo; update foo set f3 = 'zz'; select * from foo; select f3,max(f1) from foo group by f3; + +-- Simple tests for alter table column type +alter table foo alter f1 TYPE integer; -- fails +alter table foo alter f1 TYPE varchar(10); + +create table anothertab (atcol1 serial8, atcol2 boolean, + constraint anothertab_chk check (atcol1 <= 3)); + +insert into anothertab (atcol1, atcol2) values (default, true); +insert into anothertab (atcol1, atcol2) values (default, false); +select * from anothertab; + +alter table anothertab alter column atcol1 type boolean; -- fails +alter table anothertab alter column atcol1 type integer; + +select * from anothertab; + +insert into anothertab (atcol1, atcol2) values (45, null); -- fails +insert into anothertab (atcol1, atcol2) values (default, null); + +select * from anothertab; + +alter table anothertab alter column atcol2 type text + using case when atcol2 is true then 'IT WAS TRUE' + when atcol2 is false then 'IT WAS FALSE' + else 'IT WAS NULL!' end; + +select * from anothertab; +alter table anothertab alter column atcol1 type boolean + using case when atcol1 % 2 = 0 then true else false end; -- fails +alter table anothertab alter column atcol1 drop default; +alter table anothertab alter column atcol1 type boolean + using case when atcol1 % 2 = 0 then true else false end; -- fails +alter table anothertab drop constraint anothertab_chk; + +alter table anothertab alter column atcol1 type boolean + using case when atcol1 % 2 = 0 then true else false end; + +select * from anothertab; + +drop table anothertab; + +create table another (f1 int, f2 text); + +insert into another values(1, 'one'); +insert into another values(2, 'two'); +insert into another values(3, 'three'); + +select * from another; + +alter table another + alter f1 type text using f2 || ' more', + alter f2 type bigint using f1 * 10; + +select * from another; + +drop table another; diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql index 34fb787680..ad1274c7f8 100644 --- a/src/test/regress/sql/foreign_key.sql +++ b/src/test/regress/sql/foreign_key.sql @@ -97,6 +97,12 @@ UPDATE PKTABLE SET ptest1=1 WHERE ptest1=2; -- Check FKTABLE for update of matched row SELECT * FROM FKTABLE; +-- Try altering the column type where foreign keys are involved +ALTER TABLE PKTABLE ALTER COLUMN ptest1 TYPE bigint; +ALTER TABLE FKTABLE ALTER COLUMN ftest1 TYPE bigint; +SELECT * FROM PKTABLE; +SELECT * FROM FKTABLE; + DROP TABLE PKTABLE CASCADE; DROP TABLE FKTABLE; diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql index 57f18673bf..7bfe6cb7f2 100644 --- a/src/test/regress/sql/inherit.sql +++ b/src/test/regress/sql/inherit.sql @@ -142,3 +142,10 @@ CREATE TABLE inhf (LIKE inhx, LIKE inhx); /* Throw error */ CREATE TABLE inhf (LIKE inhx INCLUDING DEFAULTS); INSERT INTO inhf DEFAULT VALUES; SELECT * FROM inhf; /* Single entry with value 'text' */ + +-- Test changing the type of inherited columns +insert into d values('test','one','two','three'); + +alter table a alter column aa type integer using bit_length(aa); + +select * from d;