diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index c249ffbc546f269eaa3081c3680a75f88e755586..6119a150626c1123a1c0bd41440699c1b7c0384d 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -1,5 +1,5 @@ <!-- -$PostgreSQL: pgsql/doc/src/sgml/ref/alter_table.sgml,v 1.68 2004/03/24 09:49:20 neilc Exp $ +$PostgreSQL: pgsql/doc/src/sgml/ref/alter_table.sgml,v 1.69 2004/05/05 04:48:45 tgl Exp $ PostgreSQL documentation --> @@ -21,31 +21,26 @@ PostgreSQL documentation <refsynopsisdiv> <synopsis> ALTER TABLE [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [ * ] - ADD [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> <replaceable class="PARAMETER">type</replaceable> [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ] + <replaceable class="PARAMETER">action</replaceable> [, ... ] ALTER TABLE [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [ * ] + RENAME [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> TO <replaceable class="PARAMETER">new_column</replaceable> +ALTER TABLE <replaceable class="PARAMETER">name</replaceable> + RENAME TO <replaceable class="PARAMETER">new_name</replaceable> + +where <replaceable class="PARAMETER">action</replaceable> is one of: + + ADD [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> <replaceable class="PARAMETER">type</replaceable> [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ] DROP [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> [ RESTRICT | CASCADE ] -ALTER TABLE [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [ * ] - ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> { SET DEFAULT <replaceable class="PARAMETER">expression</replaceable> | DROP DEFAULT } -ALTER TABLE [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [ * ] + ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> TYPE <replaceable class="PARAMETER">type</replaceable> [ USING <replaceable class="PARAMETER">expression</replaceable> ] + ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> SET DEFAULT <replaceable class="PARAMETER">expression</replaceable> + ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> DROP DEFAULT ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> { SET | DROP } NOT NULL -ALTER TABLE [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [ * ] ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> SET STATISTICS <replaceable class="PARAMETER">integer</replaceable> -ALTER TABLE [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [ * ] ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN } -ALTER TABLE [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [ * ] - SET WITHOUT OIDS -ALTER TABLE [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [ * ] - RENAME [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> TO <replaceable - class="PARAMETER">new_column</replaceable> -ALTER TABLE <replaceable class="PARAMETER">name</replaceable> - RENAME TO <replaceable class="PARAMETER">new_name</replaceable> -ALTER TABLE [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [ * ] ADD <replaceable class="PARAMETER">table_constraint</replaceable> -ALTER TABLE [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [ * ] DROP CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ] -ALTER TABLE <replaceable class="PARAMETER">name</replaceable> + SET WITHOUT OIDS OWNER TO <replaceable class="PARAMETER">new_owner</replaceable> -ALTER TABLE <replaceable class="PARAMETER">name</replaceable> CLUSTER ON <replaceable class="PARAMETER">index_name</replaceable> </synopsis> </refsynopsisdiv> @@ -81,6 +76,23 @@ ALTER TABLE <replaceable class="PARAMETER">name</replaceable> </listitem> </varlistentry> + <varlistentry> + <term><literal>ALTER COLUMN TYPE</literal></term> + <listitem> + <para> + 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 <literal>USING</literal> + 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 <literal>USING</literal> + clause must be provided if there is no implicit or assignment + cast from old to new type. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><literal>SET</literal>/<literal>DROP DEFAULT</literal></term> <listitem> @@ -147,53 +159,42 @@ ALTER TABLE <replaceable class="PARAMETER">name</replaceable> </varlistentry> <varlistentry> - <term><literal>SET WITHOUT OIDS</literal></term> + <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable></literal></term> <listitem> <para> - This form removes the <literal>oid</literal> system column from the - table. This is exactly equivalent to - <literal>DROP COLUMN oid RESTRICT</literal>, - except that it will not complain if there is already no - <literal>oid</literal> column. - </para> - - <para> - Note that there is no variant of <command>ALTER TABLE</command> - that allows OIDs to be restored to a table once they have been - removed. + This form adds a new constraint to a table using the same syntax as + <xref linkend="SQL-CREATETABLE" endterm="SQL-CREATETABLE-TITLE">. </para> </listitem> </varlistentry> <varlistentry> - <term><literal>RENAME</literal></term> + <term><literal>DROP CONSTRAINT</literal></term> <listitem> <para> - The <literal>RENAME</literal> 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. + 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. </para> </listitem> </varlistentry> <varlistentry> - <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable></literal></term> + <term><literal>SET WITHOUT OIDS</literal></term> <listitem> <para> - This form adds a new constraint to a table using the same syntax as - <xref linkend="SQL-CREATETABLE" endterm="SQL-CREATETABLE-TITLE">. + This form removes the <literal>oid</literal> system column from the + table. This is exactly equivalent to + <literal>DROP COLUMN oid RESTRICT</literal>, + except that it will not complain if there is already no + <literal>oid</literal> column. </para> - </listitem> - </varlistentry> - <varlistentry> - <term><literal>DROP CONSTRAINT</literal></term> - <listitem> <para> - 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. + Note that there is no variant of <command>ALTER TABLE</command> + that allows OIDs to be restored to a table once they have been + removed. </para> </listitem> </varlistentry> @@ -212,15 +213,34 @@ ALTER TABLE <replaceable class="PARAMETER">name</replaceable> <term><literal>CLUSTER</literal></term> <listitem> <para> - This form marks a table for future <xref linkend="SQL-CLUSTER" endterm="sql-cluster-title"> + This form selects the default controlling index for future <xref linkend="SQL-CLUSTER" endterm="sql-cluster-title"> operations. </para> </listitem> </varlistentry> + <varlistentry> + <term><literal>RENAME</literal></term> + <listitem> + <para> + The <literal>RENAME</literal> 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. + </para> + </listitem> + </varlistentry> + </variablelist> </para> + <para> + All the actions except <literal>RENAME</literal> 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. + </para> + <para> You must own the table to use <command>ALTER TABLE</>; except for <command>ALTER TABLE OWNER</>, which may only be executed by a superuser. @@ -262,7 +282,8 @@ ALTER TABLE <replaceable class="PARAMETER">name</replaceable> <term><replaceable class="PARAMETER">type</replaceable></term> <listitem> <para> - Data type of the new column. + Data type of the new column, or new data type for an existing + column. </para> </listitem> </varlistentry> @@ -352,16 +373,27 @@ ALTER TABLE <replaceable class="PARAMETER">name</replaceable> </para> <para> - In the current implementation of <literal>ADD COLUMN</literal>, - default and <literal>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 <literal>SET DEFAULT</literal> form - of <command>ALTER TABLE</command> to set the default afterward. - (You may also want to update the already existing rows to the - new default value, using - <xref linkend="sql-update" endterm="sql-update-title">.) - If you want to mark the column non-null, use the <literal>SET NOT NULL</> - form after you've entered non-null values for the column in all rows. + When a column is added with <literal>ADD COLUMN</literal>, all existing + rows in the table are initialized with the column's default value + (NULL if no <literal>DEFAULT</> clause is specified). + </para> + + <para> + 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. + </para> + + <para> + Adding a <literal>CHECK</> or <literal>NOT NULL</> constraint requires + scanning the table to verify that existing rows meet the constraint. + </para> + + <para> + The main reason for providing the option to specify multiple changes + in a single <command>ALTER TABLE</> is that multiple table scans or + rewrites can thereby be combined into a single pass over the table. </para> <para> @@ -381,9 +413,9 @@ VACUUM FULL table; </para> <para> - 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, <command>ALTER TABLE ONLY</command> + 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, <command>ALTER TABLE ONLY</command> will be rejected. This ensures that the descendants always have columns matching the parent. </para> @@ -427,6 +459,15 @@ ALTER TABLE distributors DROP COLUMN address RESTRICT; </programlisting> </para> + <para> + To change the types of two existing columns in one operation: +<programlisting> +ALTER TABLE distributors + ALTER COLUMN address TYPE varchar(80), + ALTER COLUMN name TYPE varchar(100); +</programlisting> + </para> + <para> To rename an existing column: <programlisting> @@ -493,15 +534,11 @@ ALTER TABLE distributors ADD PRIMARY KEY (dist_id); <title>Compatibility</title> <para> - The <literal>ADD COLUMN</literal> form conforms with the SQL - standard, with the exception that it does not support defaults and - not-null constraints, as explained above. The <literal>ALTER - COLUMN</literal> form is in full conformance. - </para> - - <para> - The clauses to rename tables, columns, indexes, views, and sequences are + The <literal>ADD</literal>, <literal>DROP</>, and <literal>SET DEFAULT</> + forms conform with the SQL standard. The other forms are <productname>PostgreSQL</productname> extensions of the SQL standard. + Also, the ability to specify more than one manipulation in a single + <command>ALTER TABLE</> command is an extension. </para> <para> diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y index e6bde26d33726422705d52fd448a9deb82f21b62..679ef5577fab6751b6d9a9fab75c61bafc7ad45c 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 e6552b9f4736940f849203612f7eac269f695903..5f2b2ea28a070b816449d3858861d820a9004de4 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 087e2d7f23cd312fbe3951ba98b6556ff0047619..51b7d31772aff88853ca9a49564809a55dc8262d 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 86076960a4096d88825bc2d256660211c09e2cc6..5a8d5b226718dac320f8b82069ecfcb1ef42c0cd 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 a10bbbd746cb258c27be34fd7a5c4d62a15bef18..b4bf08006ebb31cdee59ba08441c0856b7aa4559 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); - - baseobject.objectId = r2; - toastobject.objectId = relform2->reltoastrelid; + if (relform1->reltoastrelid) + { + baseobject.objectId = r1; + toastobject.objectId = relform1->reltoastrelid; + recordDependencyOn(&toastobject, &baseobject, DEPENDENCY_INTERNAL); + } - 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 507bcf50d5fb3d85600e5c50bb66b728d38e57cd..efcc70c68560cbabb704feaedd71a29760e17b0c 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 a6e3a93d349512634f89184c89ec2d4484c14472..a9c307b80c30f86ebc66e28bc9e9a4c3781caec5 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,340 +1683,803 @@ update_ri_trigger_args(Oid relid, CommandCounterIncrement(); } +/* + * AlterTable + * Execute ALTER TABLE, which can be a list of subcommands + * + * 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 +AlterTable(AlterTableStmt *stmt) +{ + ATController(relation_openrv(stmt->relation, AccessExclusiveLock), + stmt->cmds, + interpretInhOption(stmt->relation->inhOpt)); +} -/* ---------------- - * AlterTableAddColumn - * (formerly known as PerformAddAttribute) +/* + * AlterTableInternal * - * adds an additional attribute to a relation - * ---------------- + * ALTER TABLE with target specified by OID */ void -AlterTableAddColumn(Oid myrelid, - bool recurse, - ColumnDef *colDef) +AlterTableInternal(Oid relid, List *cmds, bool recurse) { - Relation rel, - pgclass, - attrdesc; - HeapTuple reltup; - HeapTuple newreltup; - HeapTuple attributeTuple; - Form_pg_attribute attribute; - FormData_pg_attribute attributeD; - int i; - int minattnum, - maxatts; - HeapTuple typeTuple; - Form_pg_type tform; - int attndims; - ObjectAddress myself, - referenced; + ATController(relation_open(relid, AccessExclusiveLock), + cmds, + recurse); +} - /* - * Grab an exclusive lock on the target table, which we will NOT - * release until end of transaction. - */ - rel = heap_open(myrelid, AccessExclusiveLock); +static void +ATController(Relation rel, List *cmds, bool recurse) +{ + List *wqueue = NIL; + List *lcmd; - if (rel->rd_rel->relkind != RELKIND_RELATION) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is not a table", - RelationGetRelationName(rel)))); + /* 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); /* - * 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. + * 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). */ - 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)))); + cmd = copyObject(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. + * Do permissions checking, recursion to child tables if needed, + * and any additional phase-1 processing needed. */ - if (recurse) + switch (cmd->subtype) { - List *child, - *children; - ColumnDef *colDefChild = copyObject(colDef); + 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); - /* Child should see column as singly inherited */ - colDefChild->inhcount = 1; - colDefChild->is_local = false; + 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; + } - /* We only want direct inheritors */ - children = find_inheritance_children(myrelid); + /* Add the subcommand to the appropriate list for phase 2 */ + tab->subcmds[pass] = lappend(tab->subcmds[pass], cmd); +} - foreach(child, children) +/* + * 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) { - Oid childrelid = lfirsto(child); - HeapTuple tuple; - Form_pg_attribute childatt; - Relation childrel; + AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab); + List *subcmds = tab->subcmds[pass]; + Relation rel; + List *lcmd; - if (childrelid == myrelid) + if (subcmds == NIL) continue; - childrel = heap_open(childrelid, AccessExclusiveLock); + /* Exclusive lock was obtained by phase 1, needn't get it again */ + rel = relation_open(tab->relid, NoLock); - /* Does child already have a column by this name? */ - attrdesc = heap_openr(AttributeRelationName, RowExclusiveLock); - tuple = SearchSysCacheCopyAttName(childrelid, colDef->colname); - if (!HeapTupleIsValid(tuple)) + foreach(lcmd, subcmds) { - /* No, recurse to add it normally */ - heap_close(attrdesc, RowExclusiveLock); - heap_close(childrel, NoLock); - AlterTableAddColumn(childrelid, true, colDefChild); - continue; + ATExecCmd(tab, rel, (AlterTableCmd *) lfirst(lcmd)); } - 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 + * 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). */ - ereport(NOTICE, - (errmsg("merging definition of column \"%s\" for child \"%s\"", - colDef->colname, get_rel_name(childrelid)))); + if (pass == AT_PASS_ALTER_TYPE) + ATPostAlterTypeCleanup(wqueue, tab); - /* Bump the existing child att's inhcount */ - childatt->attinhcount++; - simple_heap_update(attrdesc, &tuple->t_self, tuple); - CatalogUpdateIndexes(attrdesc, tuple); + relation_close(rel, NoLock); + } + } - /* - * Propagate any new CHECK constraints into the child table - * and its descendants - */ - if (colDef->constraints != NIL) - { - CommandCounterIncrement(); - AlterTableAddConstraint(childrelid, true, colDef->constraints); - } + /* + * 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); - heap_freetuple(tuple); - heap_close(attrdesc, RowExclusiveLock); - heap_close(childrel, NoLock); + if (tab->subcmds[AT_PASS_ADD_COL] || + tab->subcmds[AT_PASS_ALTER_TYPE] || + tab->subcmds[AT_PASS_COL_ATTRS]) + { + AlterTableCreateToastTable(tab->relid, true); } } - else +} + +/* + * ATExecCmd: dispatch a subcommand to appropriate execution routine + */ +static void +ATExecCmd(AlteredTableInfo *tab, Relation rel, AlterTableCmd *cmd) +{ + switch (cmd->subtype) { - /* - * 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) - ereport(ERROR, - (errcode(ERRCODE_INVALID_TABLE_DEFINITION), - errmsg("column must be added to child tables too"))); + 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; } /* - * 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)). + * Bump the command counter to ensure the next subcommand in the sequence + * can see the changes so far */ - 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."))); + CommandCounterIncrement(); +} - 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."))); +/* + * ATRewriteTables: ALTER TABLE phase 3 + */ +static void +ATRewriteTables(List **wqueue) +{ + List *ltab; - pgclass = heap_openr(RelationRelationName, RowExclusiveLock); + /* Go through each table that needs to be checked or rewritten */ + foreach(ltab, *wqueue) + { + AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab); - reltup = SearchSysCache(RELOID, - ObjectIdGetDatum(myrelid), - 0, 0, 0); - if (!HeapTupleIsValid(reltup)) - elog(ERROR, "cache lookup failed for relation %u", myrelid); + /* + * 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); - /* - * this test is deliberately not attisdropped-aware, since if one - * tries to add a column matching a dropped column name, it's gonna - * fail anyway. - */ - if (SearchSysCacheExists(ATTNAME, - ObjectIdGetDatum(myrelid), - PointerGetDatum(colDef->colname), - 0, 0)) - ereport(ERROR, - (errcode(ERRCODE_DUPLICATE_COLUMN), - errmsg("column \"%s\" of relation \"%s\" already exists", - colDef->colname, RelationGetRelationName(rel)))); + /* + * 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); - minattnum = ((Form_pg_class) GETSTRUCT(reltup))->relnatts; - maxatts = minattnum + 1; - if (maxatts > MaxHeapAttributeNumber) - ereport(ERROR, - (errcode(ERRCODE_TOO_MANY_COLUMNS), - errmsg("tables can have at most %d columns", - MaxHeapAttributeNumber))); - i = minattnum + 1; + OIDNewHeap = make_new_heap(tab->relid, NewHeapName); - attrdesc = heap_openr(AttributeRelationName, RowExclusiveLock); + /* + * 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); - if (colDef->typename->arrayBounds) - attndims = length(colDef->typename->arrayBounds); - else - attndims = 0; + /* Swap the relfilenodes of the old and new heaps. */ + swap_relfilenodes(tab->relid, OIDNewHeap); - typeTuple = typenameType(colDef->typename); - tform = (Form_pg_type) GETSTRUCT(typeTuple); + CommandCounterIncrement(); - /* make sure datatype is legal for a column */ - CheckAttributeType(colDef->colname, HeapTupleGetOid(typeTuple)); + /* Destroy new heap with old filenode */ + object.classId = RelOid_pg_class; + object.objectId = OIDNewHeap; + object.objectSubId = 0; - attributeTuple = heap_addheader(Natts_pg_attribute, - false, - ATTRIBUTE_TUPLE_SIZE, - (void *) &attributeD); + /* + * 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 */ - attribute = (Form_pg_attribute) GETSTRUCT(attributeTuple); + /* + * 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); + } + } - attribute->attrelid = myrelid; - namestrcpy(&(attribute->attname), colDef->colname); - attribute->atttypid = HeapTupleGetOid(typeTuple); - attribute->attstattarget = -1; - attribute->attlen = tform->typlen; - attribute->attcacheoff = -1; - attribute->atttypmod = colDef->typename->typmod; - attribute->attnum = i; - attribute->attbyval = tform->typbyval; - attribute->attndims = attndims; - 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->attisdropped = false; - attribute->attislocal = colDef->is_local; - attribute->attinhcount = colDef->inhcount; + /* + * 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; - ReleaseSysCache(typeTuple); + foreach(lcon, tab->constraints) + { + NewConstraint *con = lfirst(lcon); - simple_heap_insert(attrdesc, attributeTuple); + if (con->contype == CONSTR_FOREIGN) + { + FkConstraint *fkconstraint = (FkConstraint *) con->qual; + Relation refrel; - /* Update indexes on pg_attribute */ - CatalogUpdateIndexes(attrdesc, attributeTuple); + if (rel == NULL) + { + /* Long since locked, no need for another */ + rel = heap_open(tab->relid, NoLock); + } - heap_close(attrdesc, RowExclusiveLock); + 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; + List *l; + EState *estate; /* - * Update number of attributes in pg_class tuple + * Open the relation(s). We have surely already locked the existing + * table. + */ + 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 */ - newreltup = heap_copytuple(reltup); - ((Form_pg_class) GETSTRUCT(newreltup))->relnatts = maxatts; + estate = CreateExecutorState(); - simple_heap_update(pgclass, &newreltup->t_self, newreltup); + /* Build the needed expression execution states */ + foreach(l, tab->constraints) + { + NewConstraint *con = lfirst(l); - /* keep catalog indexes current */ - CatalogUpdateIndexes(pgclass, newreltup); + 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); + } + } - heap_freetuple(newreltup); - ReleaseSysCache(reltup); + foreach(l, tab->newvals) + { + NewColumnValue *ex = lfirst(l); - heap_close(pgclass, RowExclusiveLock); + needscan = true; - heap_close(rel, NoLock); /* close rel but keep lock! */ + ex->exprstate = ExecPrepareExpr((Expr *) ex->expr, estate); + } - /* - * Add datatype dependency for the new column. - */ - myself.classId = RelOid_pg_class; - myself.objectId = myrelid; - myself.objectSubId = i; - referenced.classId = RelOid_pg_type; - referenced.objectId = attribute->atttypid; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + if (needscan) + { + ExprContext *econtext; + Datum *values; + char *nulls; + TupleTableSlot *oldslot; + TupleTableSlot *newslot; + HeapScanDesc scan; + HeapTuple tuple; - /* - * Make our catalog updates visible for subsequent steps. - */ - CommandCounterIncrement(); + econtext = GetPerTupleExprContext(estate); - /* - * 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) + /* + * 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) { - rel = heap_open(myrelid, AccessExclusiveLock); - AddRelationRawConstraints(rel, NIL, colDef->constraints); - heap_close(rel, NoLock); + tab = (AlteredTableInfo *) lfirst(ltab); + if (tab->relid == relid) + return tab; } /* - * Automatically create the secondary relation for TOAST if it - * formerly had no such but now has toastable attributes. + * Not there, so add it. Note that we make a copy of the relation's + * existing descriptor before anything interesting can happen to it. */ - AlterTableCreateToastTable(myrelid, true); + tab = (AlteredTableInfo *) palloc0(sizeof(AlteredTableInfo)); + tab->relid = relid; + tab->oldDesc = CreateTupleDescCopy(RelationGetDescr(rel)); + + *wqueue = lappend(*wqueue, tab); + + return tab; } /* - * ALTER TABLE ALTER COLUMN DROP NOT NULL + * 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 */ -void -AlterTableAlterColumnDropNotNull(Oid myrelid, bool recurse, - const char *colName) +static void +ATSimplePermissions(Relation rel, bool allowView) { - 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)))); + { + 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 checks */ - if (!pg_class_ownercheck(myrelid, GetUserId())) + if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(rel)); @@ -1901,17 +2488,32 @@ AlterTableAlterColumnDropNotNull(Oid myrelid, bool recurse, (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 + * Propagate to children if desired. Non-table relations never have + * children, so no need to search in that case. */ - if (recurse) + 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(myrelid); + children = find_all_inheritors(relid); /* * find_all_inheritors does the recursive search of the @@ -1921,292 +2523,472 @@ AlterTableAlterColumnDropNotNull(Oid myrelid, bool recurse, foreach(child, children) { Oid childrelid = lfirsto(child); + Relation childrel; - if (childrelid == myrelid) + if (childrelid == relid) continue; - AlterTableAlterColumnDropNotNull(childrelid, - false, colName); + childrel = relation_open(childrelid, AccessExclusiveLock); + ATPrepCmd(wqueue, childrel, cmd, false, true); + relation_close(childrel, NoLock); } } +} - /* now do the thing on this relation */ +/* + * 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; - /* - * get the number of the attribute - */ - attnum = get_attnum(myrelid, colName); - if (attnum == InvalidAttrNumber) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("column \"%s\" of relation \"%s\" does not exist", - colName, RelationGetRelationName(rel)))); + /* this routine is actually in the planner */ + children = find_inheritance_children(relid); - /* Prevent them from altering a system attribute */ - if (attnum < 0) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot alter system column \"%s\"", - colName))); + 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) +{ /* - * Check that the attribute is not in a primary key + * Recurse to add the column to child classes, if requested. + * + * 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) + { + AlterTableCmd *childCmd = copyObject(cmd); + ColumnDef *colDefChild = (ColumnDef *) childCmd->def; - /* Loop over all indexes on the relation */ - indexoidlist = RelationGetIndexList(rel); + /* Child should see column as singly inherited */ + colDefChild->inhcount = 1; + colDefChild->is_local = false; - foreach(indexoidscan, indexoidlist) + ATOneLevelRecursion(wqueue, rel, childCmd); + } + else { - Oid indexoid = lfirsto(indexoidscan); - HeapTuple indexTuple; - Form_pg_index indexStruct; - int i; - - indexTuple = SearchSysCache(INDEXRELID, - ObjectIdGetDatum(indexoid), - 0, 0, 0); - if (!HeapTupleIsValid(indexTuple)) - elog(ERROR, "cache lookup failed for index %u", indexoid); - indexStruct = (Form_pg_index) GETSTRUCT(indexTuple); + /* + * 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(RelationGetRelid(rel)) != NIL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("column must be added to child tables too"))); + } +} - /* If the index is not a primary key, skip the check */ - if (indexStruct->indisprimary) - { - /* - * Loop over each attribute in the primary key and see if it - * matches the to-be-altered attribute - */ - for (i = 0; i < indexStruct->indnatts; i++) - { - if (indexStruct->indkey[i] == attnum) - ereport(ERROR, - (errcode(ERRCODE_INVALID_TABLE_DEFINITION), - errmsg("column \"%s\" is in a primary key", - colName))); - } - } - - ReleaseSysCache(indexTuple); - } +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; - freeList(indexoidlist); + attrdesc = heap_openr(AttributeRelationName, RowExclusiveLock); /* - * Okay, actually perform the catalog change + * Are we adding the column to a recursion child? If so, check whether + * to merge with an existing definition for the column. */ - attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock); + if (colDef->inhcount > 0) + { + HeapTuple tuple; - tuple = SearchSysCacheCopyAttName(myrelid, colName); - if (!HeapTupleIsValid(tuple)) /* shouldn't happen */ - elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u", - colName, myrelid); + /* 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); - ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = FALSE; + /* 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))); - simple_heap_update(attr_rel, &tuple->t_self, tuple); + /* Bump the existing child att's inhcount */ + childatt->attinhcount++; + simple_heap_update(attrdesc, &tuple->t_self, tuple); + CatalogUpdateIndexes(attrdesc, tuple); - /* keep the system catalog indexes current */ - CatalogUpdateIndexes(attr_rel, tuple); + heap_freetuple(tuple); - heap_close(attr_rel, RowExclusiveLock); + /* Inform the user about the merge */ + ereport(NOTICE, + (errmsg("merging definition of column \"%s\" for child \"%s\"", + colDef->colname, RelationGetRelationName(rel)))); - heap_close(rel, NoLock); -} + heap_close(attrdesc, RowExclusiveLock); + return; + } + } -/* - * ALTER TABLE ALTER COLUMN SET NOT NULL - */ -void -AlterTableAlterColumnSetNotNull(Oid myrelid, bool recurse, - const char *colName) -{ - Relation rel; - HeapTuple tuple; - AttrNumber attnum; - Relation attr_rel; - HeapScanDesc scan; - TupleDesc tupdesc; + pgclass = heap_openr(RelationRelationName, RowExclusiveLock); - rel = heap_open(myrelid, AccessExclusiveLock); + reltup = SearchSysCacheCopy(RELOID, + ObjectIdGetDatum(myrelid), + 0, 0, 0); + if (!HeapTupleIsValid(reltup)) + elog(ERROR, "cache lookup failed for relation %u", myrelid); - if (rel->rd_rel->relkind != RELKIND_RELATION) + /* + * this test is deliberately not attisdropped-aware, since if one + * tries to add a column matching a dropped column name, it's gonna + * fail anyway. + */ + if (SearchSysCacheExists(ATTNAME, + ObjectIdGetDatum(myrelid), + PointerGetDatum(colDef->colname), + 0, 0)) 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)); + (errcode(ERRCODE_DUPLICATE_COLUMN), + errmsg("column \"%s\" of relation \"%s\" already exists", + colDef->colname, RelationGetRelationName(rel)))); - if (!allowSystemTableMods && IsSystemRelation(rel)) + minattnum = ((Form_pg_class) GETSTRUCT(reltup))->relnatts; + maxatts = minattnum + 1; + if (maxatts > MaxHeapAttributeNumber) ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("permission denied: \"%s\" is a system catalog", - RelationGetRelationName(rel)))); + (errcode(ERRCODE_TOO_MANY_COLUMNS), + errmsg("tables can have at most %d columns", + MaxHeapAttributeNumber))); + i = minattnum + 1; - /* - * Propagate to children if desired - */ - if (recurse) - { - List *child, - *children; + typeTuple = typenameType(colDef->typename); + tform = (Form_pg_type) GETSTRUCT(typeTuple); - /* this routine is actually in the planner */ - children = find_all_inheritors(myrelid); + /* make sure datatype is legal for a column */ + CheckAttributeType(colDef->colname, HeapTupleGetOid(typeTuple)); - /* - * 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); + attributeTuple = heap_addheader(Natts_pg_attribute, + false, + ATTRIBUTE_TUPLE_SIZE, + (void *) &attributeD); - if (childrelid == myrelid) - continue; - AlterTableAlterColumnSetNotNull(childrelid, - false, colName); - } - } + attribute = (Form_pg_attribute) GETSTRUCT(attributeTuple); - /* now do the thing on this relation */ + attribute->attrelid = myrelid; + namestrcpy(&(attribute->attname), colDef->colname); + attribute->atttypid = HeapTupleGetOid(typeTuple); + attribute->attstattarget = -1; + attribute->attlen = tform->typlen; + attribute->attcacheoff = -1; + attribute->atttypmod = colDef->typename->typmod; + attribute->attnum = i; + attribute->attbyval = tform->typbyval; + attribute->attndims = length(colDef->typename->arrayBounds); + attribute->attstorage = tform->typstorage; + attribute->attalign = tform->typalign; + attribute->attnotnull = colDef->is_not_null; + attribute->atthasdef = false; + attribute->attisdropped = false; + attribute->attislocal = colDef->is_local; + attribute->attinhcount = colDef->inhcount; - /* - * get the number of the attribute - */ - attnum = get_attnum(myrelid, colName); - if (attnum == InvalidAttrNumber) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("column \"%s\" of relation \"%s\" does not exist", - colName, RelationGetRelationName(rel)))); + ReleaseSysCache(typeTuple); - /* Prevent them from altering a system attribute */ - if (attnum < 0) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot alter system column \"%s\"", - colName))); + simple_heap_insert(attrdesc, attributeTuple); + + /* Update indexes on pg_attribute */ + CatalogUpdateIndexes(attrdesc, attributeTuple); + + heap_close(attrdesc, RowExclusiveLock); /* - * Perform a scan to ensure that there are no NULL values already in - * the relation + * Update number of attributes in pg_class tuple */ - tupdesc = RelationGetDescr(rel); + ((Form_pg_class) GETSTRUCT(reltup))->relnatts = maxatts; - scan = heap_beginscan(rel, SnapshotNow, 0, NULL); + simple_heap_update(pgclass, &reltup->t_self, reltup); - while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) - { - Datum d; - bool isnull; + /* keep catalog indexes current */ + CatalogUpdateIndexes(pgclass, reltup); - d = heap_getattr(tuple, attnum, tupdesc, &isnull); + heap_freetuple(reltup); - if (isnull) - ereport(ERROR, - (errcode(ERRCODE_NOT_NULL_VIOLATION), - errmsg("column \"%s\" contains null values", - colName))); - } + heap_close(pgclass, RowExclusiveLock); - heap_endscan(scan); + /* Make the attribute's catalog entry visible */ + CommandCounterIncrement(); /* - * Okay, actually perform the catalog change + * Store the DEFAULT, if any, in the catalogs */ - attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock); + if (colDef->raw_default) + { + RawColumnDefault *rawEnt; - tuple = SearchSysCacheCopyAttName(myrelid, colName); - if (!HeapTupleIsValid(tuple)) /* shouldn't happen */ - elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u", - colName, myrelid); + rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault)); + rawEnt->attnum = attribute->attnum; + rawEnt->raw_default = copyObject(colDef->raw_default); - ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = TRUE; + /* + * This function is intended for CREATE TABLE, so it processes a + * _list_ of defaults, but we just do one. + */ + AddRelationRawConstraints(rel, makeList1(rawEnt), NIL); - simple_heap_update(attr_rel, &tuple->t_self, tuple); + /* Make the additional catalog changes visible */ + CommandCounterIncrement(); + } - /* keep the system catalog indexes current */ - CatalogUpdateIndexes(attr_rel, tuple); + /* + * 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; - heap_close(attr_rel, RowExclusiveLock); + newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue)); + newval->attnum = attribute->attnum; + newval->expr = defval; - heap_close(rel, NoLock); + tab->newvals = lappend(tab->newvals, newval); + } + + /* + * Add datatype dependency for the new column. + */ + add_column_datatype_dependency(myrelid, i, attribute->atttypid); } /* - * ALTER TABLE ALTER COLUMN SET/DROP DEFAULT + * Install a column's dependency on its datatype. */ -void -AlterTableAlterColumnDefault(Oid myrelid, bool recurse, - const char *colName, - Node *newDefault) +static void +add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid) { - Relation rel; - AttrNumber attnum; + ObjectAddress myself, + referenced; - rel = heap_open(myrelid, AccessExclusiveLock); + myself.classId = RelOid_pg_class; + myself.objectId = relid; + myself.objectSubId = attnum; + referenced.classId = RelOid_pg_type; + referenced.objectId = typid; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); +} + +/* + * ALTER TABLE ALTER COLUMN DROP NOT NULL + */ +static void +ATExecDropNotNull(Relation rel, const char *colName) +{ + HeapTuple tuple; + AttrNumber attnum; + Relation attr_rel; + List *indexoidlist; + List *indexoidscan; /* - * 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. + * lookup the attribute */ - if (rel->rd_rel->relkind != RELKIND_RELATION && - rel->rd_rel->relkind != RELKIND_VIEW) + attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock); + + tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); + + if (!HeapTupleIsValid(tuple)) ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is not a table or view", - RelationGetRelationName(rel)))); + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + colName, RelationGetRelationName(rel)))); - /* Permissions checks */ - if (!pg_class_ownercheck(myrelid, GetUserId())) - aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, - RelationGetRelationName(rel)); + attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum; - if (!allowSystemTableMods && IsSystemRelation(rel)) + /* Prevent them from altering a system attribute */ + if (attnum <= 0) ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("permission denied: \"%s\" is a system catalog", - RelationGetRelationName(rel)))); + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot alter system column \"%s\"", + colName))); /* - * Propagate to children if desired + * Check that the attribute is not in a primary key */ - if (recurse) - { - List *child, - *children; - /* this routine is actually in the planner */ - children = find_all_inheritors(myrelid); + /* Loop over all indexes on the relation */ + indexoidlist = RelationGetIndexList(rel); - /* - * 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); + foreach(indexoidscan, indexoidlist) + { + Oid indexoid = lfirsto(indexoidscan); + HeapTuple indexTuple; + Form_pg_index indexStruct; + int i; - if (childrelid == myrelid) - continue; - AlterTableAlterColumnDefault(childrelid, - false, colName, newDefault); + indexTuple = SearchSysCache(INDEXRELID, + ObjectIdGetDatum(indexoid), + 0, 0, 0); + if (!HeapTupleIsValid(indexTuple)) + elog(ERROR, "cache lookup failed for index %u", indexoid); + indexStruct = (Form_pg_index) GETSTRUCT(indexTuple); + + /* If the index is not a primary key, skip the check */ + if (indexStruct->indisprimary) + { + /* + * Loop over each attribute in the primary key and see if it + * matches the to-be-altered attribute + */ + for (i = 0; i < indexStruct->indnatts; i++) + { + if (indexStruct->indkey[i] == attnum) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("column \"%s\" is in a primary key", + colName))); + } } + + ReleaseSysCache(indexTuple); + } + + freeList(indexoidlist); + + /* + * Okay, actually perform the catalog change ... if needed + */ + if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull) + { + ((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); + } + + heap_close(attr_rel, RowExclusiveLock); +} + +/* + * ALTER TABLE ALTER COLUMN SET NOT NULL + */ +static void +ATExecSetNotNull(AlteredTableInfo *tab, Relation rel, + const char *colName) +{ + HeapTuple tuple; + AttrNumber attnum; + Relation attr_rel; + NewConstraint *newcon; + + /* + * lookup the attribute + */ + attr_rel = 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)))); + + attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum; + + /* Prevent them from altering a system attribute */ + if (attnum <= 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot alter system column \"%s\"", + colName))); + + /* + * Okay, actually perform the catalog change ... if needed + */ + if (! ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull) + { + ((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); + + /* 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); } - /* now do the thing on this relation */ + heap_close(attr_rel, RowExclusiveLock); +} + +/* + * ALTER TABLE ALTER COLUMN SET/DROP DEFAULT + */ +static void +ATExecColumnDefault(Relation rel, const char *colName, + Node *newDefault) +{ + AttrNumber attnum; /* * 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'; - Relation attrelation; - HeapTuple tuple; - Form_pg_attribute attrtuple; - - rel = relation_open(myrelid, AccessExclusiveLock); - /* - * Allow index for statistics case only + * 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) - { - if (rel->rd_rel->relkind != RELKIND_INDEX || *flagType != 'S') - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is not a table", - RelationGetRelationName(rel)))); - } + 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(myrelid, GetUserId())) + if (!pg_class_ownercheck(RelationGetRelid(rel), 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)))); +static void +ATExecSetStatistics(Relation rel, const char *colName, Node *newValue) +{ + int newtarget; + Relation attrelation; + HeapTuple tuple; + Form_pg_attribute attrtuple; + + Assert(IsA(newValue, Integer)); + newtarget = intVal(newValue); /* - * Check the supplied parameters before anything else + * Limit target to a sane range */ - 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))); - } - } - else if (*flagType == 'M') - { - /* STORAGE */ - char *storagemode; - - Assert(IsA(flagValue, String)); - storagemode = strVal(flagValue); - - if (strcasecmp(storagemode, "plain") == 0) - newstorage = 'p'; - else if (strcasecmp(storagemode, "external") == 0) - newstorage = 'e'; - else if (strcasecmp(storagemode, "extended") == 0) - newstorage = 'x'; - else if (strcasecmp(storagemode, "main") == 0) - newstorage = 'm'; - else - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid storage type \"%s\"", - storagemode))); - } - else + if (newtarget < -1) { - elog(ERROR, "unrecognized alter-column type flag: %c", - (int) *flagType); + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("statistics target %d is too low", + newtarget))); } - - /* - * Propagate to children if desired - */ - if (recurse && rel->rd_rel->relkind == RELKIND_RELATION) + else if (newtarget > 1000) { - 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); - } + newtarget = 1000; + ereport(WARNING, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("lowering statistics target to %d", + newtarget))); } - /* 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; - - rel = heap_open(myrelid, AccessExclusiveLock); + char *storagemode; + char newstorage; + Relation attrelation; + HeapTuple tuple; + Form_pg_attribute attrtuple; - /* - * check to see if we actually need to change anything - */ - if (rel->rd_rel->relhasoids == setOid) + Assert(IsA(newValue, String)); + storagemode = strVal(newValue); + + 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! */ - return; + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid storage type \"%s\"", + storagemode))); + newstorage = 0; /* keep compiler quiet */ } - if (setOid) - { - /* - * TODO: Generate the now required OID pg_attribute entry, and - * modify physical rows to have OIDs. - */ + 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("ALTER TABLE WITH OIDS is not yet implemented"))); - } + 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 - { - heap_close(rel, NoLock); /* close rel, but keep lock! */ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("column data type %s can only have storage PLAIN", + format_type_be(attrtuple->atttypid)))); - AlterTableDropColumn(myrelid, recurse, false, "oid", behavior); - } + 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,18 +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. + * 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 && !recursing) + 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) @@ -2554,71 +3267,50 @@ AlterTableDropColumn(Oid myrelid, bool recurse, bool recursing, 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 - * 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) - { - 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; - - if (childrelid == myrelid) - continue; - - childrel = heap_open(childrelid, AccessExclusiveLock); + if (recurse) + { + /* + * 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--; - 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); + simple_heap_update(attr_rel, &tuple->t_self, tuple); - if (childatt->attinhcount <= 0) /* shouldn't happen */ - elog(ERROR, "relation %u has non-inherited attribute \"%s\"", - childrelid, colName); + /* keep the system catalog indexes current */ + CatalogUpdateIndexes(attr_rel, tuple); - if (childatt->attinhcount == 1 && !childatt->attislocal) - { - /* Time to delete this child column, too */ - AlterTableDropColumn(childrelid, true, true, colName, behavior); + /* 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) +static void +ATExecAddConstraint(AlteredTableInfo *tab, Relation rel, Node *newConstraint) { - 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) + switch (nodeTag(newConstraint)) { - /* - * 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: { - 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); + 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: - AlterTableAddCheckConstraint(rel, constr); - break; - default: - elog(ERROR, "unrecognized constraint type: %d", - (int) constr->contype); - } - break; - } - case T_FkConstraint: + /* + * 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: { - FkConstraint *fkconstraint = (FkConstraint *) newConstraint; + List *newcons; + List *lcon; /* - * Assign or validate constraint name + * Call AddRelationRawConstraints to do the work. + * It returns a list of cooked constraints. */ - 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)))); + 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); } - 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)); + default: + elog(ERROR, "unrecognized constraint type: %d", + (int) constr->contype); + } + break; } + case T_FkConstraint: + { + FkConstraint *fkconstraint = (FkConstraint *) 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) -{ - 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; + /* + * 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); - /* - * Scan through the rows now, checking the expression at each row. - */ - scan = heap_beginscan(rel, SnapshotNow, 0, NULL); + ATAddForeignKeyConstraint(tab, rel, fkconstraint); - while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) - { - ExecStoreTuple(tuple, slot, InvalidBuffer, false); - if (!ExecQual(qualstate, econtext, true)) - { - successful = false; break; } - ResetExprContext(econtext); + 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. @@ -3554,268 +4108,800 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint, (errcode(ERRCODE_INVALID_FOREIGN_KEY), errmsg("number of referencing and referenced columns for foreign key disagree"))); - while (fk_attr != NIL) + while (fk_attr != NIL) + { + fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr)); + fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr)); + fk_attr = lnext(fk_attr); + pk_attr = lnext(pk_attr); + } + + trigobj.objectId = CreateTrigger(fk_trigger, true); + + /* Register dependency from trigger to constraint */ + recordDependencyOn(&trigobj, &constrobj, DEPENDENCY_INTERNAL); + + /* Make changes-so-far visible */ + CommandCounterIncrement(); + + /* + * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON + * DELETE action on the referenced table. + */ + fk_trigger = makeNode(CreateTrigStmt); + fk_trigger->trigname = fkconstraint->constr_name; + fk_trigger->relation = fkconstraint->pktable; + fk_trigger->before = false; + fk_trigger->row = true; + fk_trigger->actions[0] = 'd'; + fk_trigger->actions[1] = '\0'; + + fk_trigger->isconstraint = true; + fk_trigger->deferrable = fkconstraint->deferrable; + fk_trigger->initdeferred = fkconstraint->initdeferred; + fk_trigger->constrrel = myRel; + switch (fkconstraint->fk_del_action) + { + case FKCONSTR_ACTION_NOACTION: + fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del"); + break; + case FKCONSTR_ACTION_RESTRICT: + fk_trigger->deferrable = false; + fk_trigger->initdeferred = false; + fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del"); + break; + case FKCONSTR_ACTION_CASCADE: + fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del"); + break; + case FKCONSTR_ACTION_SETNULL: + fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del"); + break; + case FKCONSTR_ACTION_SETDEFAULT: + fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del"); + break; + default: + elog(ERROR, "unrecognized FK action type: %d", + (int) fkconstraint->fk_del_action); + break; + } + + fk_trigger->args = NIL; + fk_trigger->args = lappend(fk_trigger->args, + makeString(fkconstraint->constr_name)); + fk_trigger->args = lappend(fk_trigger->args, + makeString(myRel->relname)); + fk_trigger->args = lappend(fk_trigger->args, + makeString(fkconstraint->pktable->relname)); + fk_trigger->args = lappend(fk_trigger->args, + makeString(fkMatchTypeToString(fkconstraint->fk_matchtype))); + fk_attr = fkconstraint->fk_attrs; + pk_attr = fkconstraint->pk_attrs; + while (fk_attr != NIL) + { + fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr)); + fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr)); + fk_attr = lnext(fk_attr); + pk_attr = lnext(pk_attr); + } + + trigobj.objectId = CreateTrigger(fk_trigger, true); + + /* Register dependency from trigger to constraint */ + recordDependencyOn(&trigobj, &constrobj, DEPENDENCY_INTERNAL); + + /* Make changes-so-far visible */ + CommandCounterIncrement(); + + /* + * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON + * UPDATE action on the referenced table. + */ + fk_trigger = makeNode(CreateTrigStmt); + fk_trigger->trigname = fkconstraint->constr_name; + fk_trigger->relation = fkconstraint->pktable; + fk_trigger->before = false; + fk_trigger->row = true; + fk_trigger->actions[0] = 'u'; + fk_trigger->actions[1] = '\0'; + fk_trigger->isconstraint = true; + fk_trigger->deferrable = fkconstraint->deferrable; + fk_trigger->initdeferred = fkconstraint->initdeferred; + fk_trigger->constrrel = myRel; + switch (fkconstraint->fk_upd_action) + { + case FKCONSTR_ACTION_NOACTION: + fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd"); + break; + case FKCONSTR_ACTION_RESTRICT: + fk_trigger->deferrable = false; + fk_trigger->initdeferred = false; + fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd"); + break; + case FKCONSTR_ACTION_CASCADE: + fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd"); + break; + case FKCONSTR_ACTION_SETNULL: + fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd"); + break; + case FKCONSTR_ACTION_SETDEFAULT: + fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd"); + break; + default: + elog(ERROR, "unrecognized FK action type: %d", + (int) fkconstraint->fk_upd_action); + break; + } + + fk_trigger->args = NIL; + fk_trigger->args = lappend(fk_trigger->args, + makeString(fkconstraint->constr_name)); + fk_trigger->args = lappend(fk_trigger->args, + makeString(myRel->relname)); + fk_trigger->args = lappend(fk_trigger->args, + makeString(fkconstraint->pktable->relname)); + fk_trigger->args = lappend(fk_trigger->args, + makeString(fkMatchTypeToString(fkconstraint->fk_matchtype))); + fk_attr = fkconstraint->fk_attrs; + pk_attr = fkconstraint->pk_attrs; + while (fk_attr != NIL) + { + fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr)); + fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr)); + fk_attr = lnext(fk_attr); + pk_attr = lnext(pk_attr); + } + + trigobj.objectId = CreateTrigger(fk_trigger, true); + + /* Register dependency from trigger to constraint */ + recordDependencyOn(&trigobj, &constrobj, DEPENDENCY_INTERNAL); +} + +/* + * fkMatchTypeToString - + * convert FKCONSTR_MATCH_xxx code to string to use in trigger args + */ +static char * +fkMatchTypeToString(char match_type) +{ + switch (match_type) + { + case FKCONSTR_MATCH_FULL: + return pstrdup("FULL"); + case FKCONSTR_MATCH_PARTIAL: + return pstrdup("PARTIAL"); + case FKCONSTR_MATCH_UNSPECIFIED: + return pstrdup("UNSPECIFIED"); + default: + elog(ERROR, "unrecognized match type: %d", + (int) match_type); + } + return NULL; /* can't get here */ +} + +/* + * ALTER TABLE DROP CONSTRAINT + */ +static void +ATPrepDropConstraint(List **wqueue, Relation rel, + bool recurse, AlterTableCmd *cmd) +{ + /* + * We don't want errors or noise from child tables, so we have to pass + * down a modified command. + */ + if (recurse) + { + AlterTableCmd *childCmd = copyObject(cmd); + + childCmd->subtype = AT_DropConstraintQuietly; + ATSimpleRecursion(wqueue, rel, childCmd, recurse); + } +} + +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)) + { + case OCLASS_CLASS: + { + char relKind = get_rel_relkind(foundObject.objectId); + + 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 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. + */ + 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)); + + scan = systable_beginscan(depRel, DependDependerIndex, true, + SnapshotNow, 3, key); + + while (HeapTupleIsValid(depTup = systable_getnext(scan))) { - fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr)); - fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr)); - fk_attr = lnext(fk_attr); - pk_attr = lnext(pk_attr); - } + Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup); - trigobj.objectId = CreateTrigger(fk_trigger, true); + 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"); - /* Register dependency from trigger to constraint */ - recordDependencyOn(&trigobj, &constrobj, DEPENDENCY_INTERNAL); + simple_heap_delete(depRel, &depTup->t_self); + } - /* Make changes-so-far visible */ - CommandCounterIncrement(); + systable_endscan(scan); + + heap_close(depRel, RowExclusiveLock); /* - * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON - * DELETE action on the referenced table. + * Here we go --- change the recorded column type. (Note heapTup is + * a copy of the syscache entry, so okay to scribble on.) */ - fk_trigger = makeNode(CreateTrigStmt); - fk_trigger->trigname = fkconstraint->constr_name; - fk_trigger->relation = fkconstraint->pktable; - fk_trigger->before = false; - fk_trigger->row = true; - fk_trigger->actions[0] = 'd'; - fk_trigger->actions[1] = '\0'; + 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; - fk_trigger->isconstraint = true; - fk_trigger->deferrable = fkconstraint->deferrable; - fk_trigger->initdeferred = fkconstraint->initdeferred; - fk_trigger->constrrel = myRel; - switch (fkconstraint->fk_del_action) - { - case FKCONSTR_ACTION_NOACTION: - fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del"); - break; - case FKCONSTR_ACTION_RESTRICT: - fk_trigger->deferrable = false; - fk_trigger->initdeferred = false; - fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del"); - break; - case FKCONSTR_ACTION_CASCADE: - fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del"); - break; - case FKCONSTR_ACTION_SETNULL: - fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del"); - break; - case FKCONSTR_ACTION_SETDEFAULT: - fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del"); - break; - default: - elog(ERROR, "unrecognized FK action type: %d", - (int) fkconstraint->fk_del_action); - break; - } + ReleaseSysCache(typeTuple); - fk_trigger->args = NIL; - fk_trigger->args = lappend(fk_trigger->args, - makeString(fkconstraint->constr_name)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(myRel->relname)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(fkconstraint->pktable->relname)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(fkMatchTypeToString(fkconstraint->fk_matchtype))); - fk_attr = fkconstraint->fk_attrs; - pk_attr = fkconstraint->pk_attrs; - while (fk_attr != NIL) - { - fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr)); - fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr)); - fk_attr = lnext(fk_attr); - pk_attr = lnext(pk_attr); - } + simple_heap_update(attrelation, &heapTup->t_self, heapTup); - trigobj.objectId = CreateTrigger(fk_trigger, true); + /* keep system catalog indexes current */ + CatalogUpdateIndexes(attrelation, heapTup); - /* Register dependency from trigger to constraint */ - recordDependencyOn(&trigobj, &constrobj, DEPENDENCY_INTERNAL); + heap_close(attrelation, RowExclusiveLock); - /* Make changes-so-far visible */ - CommandCounterIncrement(); + /* 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); /* - * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON - * UPDATE action on the referenced table. + * 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.) */ - fk_trigger = makeNode(CreateTrigStmt); - fk_trigger->trigname = fkconstraint->constr_name; - fk_trigger->relation = fkconstraint->pktable; - fk_trigger->before = false; - fk_trigger->row = true; - fk_trigger->actions[0] = 'u'; - fk_trigger->actions[1] = '\0'; - fk_trigger->isconstraint = true; - fk_trigger->deferrable = fkconstraint->deferrable; - fk_trigger->initdeferred = fkconstraint->initdeferred; - fk_trigger->constrrel = myRel; - switch (fkconstraint->fk_upd_action) + if (defaultexpr) { - case FKCONSTR_ACTION_NOACTION: - fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd"); - break; - case FKCONSTR_ACTION_RESTRICT: - fk_trigger->deferrable = false; - fk_trigger->initdeferred = false; - fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd"); - break; - case FKCONSTR_ACTION_CASCADE: - fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd"); - break; - case FKCONSTR_ACTION_SETNULL: - fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd"); - break; - case FKCONSTR_ACTION_SETDEFAULT: - fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd"); - break; - default: - elog(ERROR, "unrecognized FK action type: %d", - (int) fkconstraint->fk_upd_action); - break; - } + /* Must make new row visible since it will be updated again */ + CommandCounterIncrement(); - fk_trigger->args = NIL; - fk_trigger->args = lappend(fk_trigger->args, - makeString(fkconstraint->constr_name)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(myRel->relname)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(fkconstraint->pktable->relname)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(fkMatchTypeToString(fkconstraint->fk_matchtype))); - fk_attr = fkconstraint->fk_attrs; - pk_attr = fkconstraint->pk_attrs; - while (fk_attr != NIL) - { - fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr)); - fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr)); - fk_attr = lnext(fk_attr); - pk_attr = lnext(pk_attr); - } + /* + * 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); - trigobj.objectId = CreateTrigger(fk_trigger, true); + StoreAttrDefault(rel, attnum, nodeToString(defaultexpr)); + } - /* Register dependency from trigger to constraint */ - recordDependencyOn(&trigobj, &constrobj, DEPENDENCY_INTERNAL); + /* Cleanup */ + heap_freetuple(heapTup); } /* - * fkMatchTypeToString - - * convert FKCONSTR_MATCH_xxx code to string to use in trigger args + * 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 char * -fkMatchTypeToString(char match_type) +static void +ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab) { - switch (match_type) + 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) { - case FKCONSTR_MATCH_FULL: - return pstrdup("FULL"); - case FKCONSTR_MATCH_PARTIAL: - return pstrdup("PARTIAL"); - case FKCONSTR_MATCH_UNSPECIFIED: - return pstrdup("UNSPECIFIED"); - default: - elog(ERROR, "unrecognized match type: %d", - (int) match_type); + ATPostAlterTypeParse((char *) lfirst(l), wqueue); + } + foreach(l, tab->changedConstraintDefs) + { + ATPostAlterTypeParse((char *) lfirst(l), wqueue); } - return NULL; /* can't get here */ -} - -/* - * ALTER TABLE DROP CONSTRAINT - */ -void -AlterTableDropConstraint(Oid myrelid, bool recurse, - const char *constrName, - DropBehavior behavior) -{ - Relation rel; - int deleted = 0; /* - * Acquire an exclusive lock on the target relation for the duration - * of the operation. + * 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. */ - rel = heap_open(myrelid, AccessExclusiveLock); + 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); + } - /* 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)))); + obj.classId = RelOid_pg_class; + foreach(l, tab->changedIndexOids) + { + obj.objectId = lfirsto(l); + obj.objectSubId = 0; + performDeletion(&obj, DROP_RESTRICT); + } - /* Permissions checks */ - if (!pg_class_ownercheck(myrelid, GetUserId())) - aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, - RelationGetRelationName(rel)); + /* + * The objects will get recreated during subsequent passes over the + * work queue. + */ +} - if (!allowSystemTableMods && IsSystemRelation(rel)) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("permission denied: \"%s\" is a system catalog", - RelationGetRelationName(rel)))); +static void +ATPostAlterTypeParse(char *cmd, List **wqueue) +{ + List *raw_parsetree_list; + List *querytree_list; + List *list_item; /* - * Process child tables if requested. + * We expect that we only have to do raw parsing and parse analysis, not + * any rule rewriting, since these will all be utility statements. */ - if (recurse) + raw_parsetree_list = raw_parser(cmd); + querytree_list = NIL; + foreach(list_item, raw_parsetree_list) { - 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); - Relation inhrel; + Node *parsetree = (Node *) lfirst(list_item); - if (childrelid == myrelid) - continue; - inhrel = heap_open(childrelid, AccessExclusiveLock); - /* do NOT count child constraints in deleted. */ - RemoveRelConstraints(inhrel, constrName, behavior); - heap_close(inhrel, NoLock); - } + querytree_list = nconc(querytree_list, + parse_analyze(parsetree, NULL, 0)); } /* - * Now do the thing on this relation. + * Attach each generated command to the proper place in the work queue. + * Note this could result in creation of entirely new work-queue entries. */ - deleted += RemoveRelConstraints(rel, constrName, behavior); + foreach(list_item, querytree_list) + { + Query *query = (Query *) lfirst(list_item); + Relation rel; + AlteredTableInfo *tab; - /* Close the target relation */ - heap_close(rel, NoLock); + 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; - /* 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))); + 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 c7d6193280ed58ca17d02dcc853b5bbf0da0affb..1466be98cbb4d65efe71af0c1a4409a181bda26f 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 900d98dc8c0f8f88d4ff52b7e6029784a54a00d0..eab30d122c2b1cf7f2cac86a3ecd4d8fcbf94ff1 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 2330bf18d43d8d34e4867813f87f237c14a1ddd3..4a251b63de4666c1155ab6dcb81b85e273f233d2 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); - - 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; - - Assert(IsA(stmt->def, ColumnDef)); - transformColumnDefinition(pstate, &cxt, - (ColumnDef *) stmt->def); - - transformIndexConstraints(pstate, &cxt); - transformFKConstraints(pstate, &cxt, false); - - ((ColumnDef *) stmt->def)->constraints = cxt.ckconstraints; - *extras_before = nconc(*extras_before, cxt.blist); - *extras_after = nconc(cxt.alist, *extras_after); - - heap_close(rel, NoLock); /* close rel, keep lock */ - break; + AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd); - case 'C': - rel = heap_openrv(stmt->relation, AccessExclusiveLock); - - 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; - - 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)); + switch (cmd->subtype) + { + case AT_AddColumn: + { + ColumnDef *def = (ColumnDef *) cmd->def; - transformIndexConstraints(pstate, &cxt); - transformFKConstraints(pstate, &cxt, true); + Assert(IsA(cmd->def, ColumnDef)); + transformColumnDefinition(pstate, &cxt, + (ColumnDef *) cmd->def); - 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); + /* + * 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; - heap_close(rel, NoLock); /* close rel, keep lock */ - break; + newcmds = lappend(newcmds, cmd); - case 'c': + /* + * 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; + + /* Add as a separate AlterTableCmd */ + newcmd = makeNode(AlterTableCmd); + newcmd->subtype = AT_SetNotNull; + newcmd->name = pstrdup(def->colname); + newcmds = lappend(newcmds, newcmd); + } - /* - * Already-transformed ADD CONSTRAINT, so just make it look - * like the standard case. - */ - stmt->subtype = 'C'; - break; + /* + * All constraints are processed in other ways. + * Remove the original list + */ + def->constraints = NIL; - default: - break; + break; + } + case AT_AddConstraint: + /* The original AddConstraint cmd node doesn't go to newcmds */ + + 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; + + case AT_ProcessedConstraint: + + /* + * Already-transformed ADD CONSTRAINT, so just make it look + * like the standard case. + */ + cmd->subtype = AT_AddConstraint; + newcmds = lappend(newcmds, cmd); + 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 de8597ed9efaa9068359c0ca8d033bae0cf80e55..64825893bc10b5a40f20da93a1018251236ff461 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 <node> select_no_parens select_with_parens select_clause simple_select -%type <node> alter_column_default opclass_item +%type <node> alter_column_default opclass_item alter_using %type <ival> add_drop +%type <node> alter_table_cmd +%type <list> alter_table_cmds + %type <dbehavior> opt_drop_behavior %type <list> createdb_opt_list copy_opt_list @@ -199,7 +202,7 @@ static void doNegateFloat(Value *v); %type <range> qualified_name OptConstrFromTable -%type <str> all_Op MathOp opt_name SpecialRuleRelation +%type <str> all_Op MathOp SpecialRuleRelation %type <str> iso_level opt_encoding %type <node> grantee @@ -1127,127 +1130,139 @@ CheckPointStmt: *****************************************************************************/ AlterTableStmt: - /* ALTER TABLE <relation> ADD [COLUMN] <coldef> */ - 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 <relation> ADD [COLUMN] <coldef> */ + ADD opt_column columnDef + { + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_AddColumn; + n->def = $3; $$ = (Node *)n; } /* ALTER TABLE <relation> ALTER [COLUMN] <colname> {SET DEFAULT <expr>|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 <relation> ALTER [COLUMN] <colname> 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 <relation> ALTER [COLUMN] <colname> 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 <relation> ALTER [COLUMN] <colname> SET STATISTICS <IntegerOnly> */ - | 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 <relation> ALTER [COLUMN] <colname> SET STORAGE <storagemode> */ - | 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 <relation> DROP [COLUMN] <colname> [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 <relation> ALTER [COLUMN] <colname> TYPE <typename> + * [ USING <expression> ] + */ + | 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 <relation> 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 <relation> DROP CONSTRAINT <name> [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 <relation> 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 <name> CREATE TOAST TABLE */ - | ALTER TABLE qualified_name CREATE TOAST TABLE + /* ALTER TABLE <name> 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 <name> 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 <name> CLUSTER ON <indexname> */ - | 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 98c63fd0e64a6583a20340507247229eee025d74..68e9f84672549231fd3f258cf39b142abd9ff3e3 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 3960152f3876cdd97ea31eb62132ea04f30a692b..412aa252d62496a818ea53bbe629e38c098e47d1 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))); +} + +/* 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 Datum +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))); } +/* 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 Datum -pg_get_constraintdef_worker(Oid constraintId, int prettyFlags) +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 583851c7493a14c7241bee9e697ac5a8cb5f4e9a..0e230d7e8628f89a5fdb8aa370f255ee33cf3ed0 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 f635ef1bd09d345e6cd33bd69be5fa3c3188f6ba..2913d53e521af0c470705dc2cbc7f19c27ae24ff 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 da210c483aea9adb70dc6e8a5ddc687b66b54865..13c367c9022845c36ccbfc8d26e6ff32cfc5cb84 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 72295803abbb7bcf730e0a877b673983d3d12c64..5474fdd54908cd85446a14c69d802937d36c936e 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 00f5fa1a4801c23e8a72d0791da80f3fc2d5e236..78fe4ab9071a0da97a6ee919286b7d31c4389001 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 889b4dfa626bd5bf9ce709a83f750c2144bd0c85..f9f03c1bd0349204118803c0dffa88e8bcfc23f8 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 Oid DefineRelation(CreateStmt *stmt, char relkind); -extern void AlterTableAddConstraint(Oid myrelid, bool recurse, - List *newConstraints); +extern void RemoveRelation(const RangeVar *relation, DropBehavior behavior); -extern void AlterTableDropConstraint(Oid myrelid, bool recurse, - const char *constrName, - DropBehavior behavior); +extern void AlterTable(AlterTableStmt *stmt); -extern void AlterTableClusterOn(Oid relOid, const char *indexName); +extern void AlterTableInternal(Oid relid, List *cmds, bool recurse); 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 TruncateRelation(const RangeVar *relation); extern void renameatt(Oid myrelid, diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index d5d4f0832a7b8e157fbe5ab2fb1924f513415ab2..a776607ec660ab56fe7025c6adda76ae3efcb556 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 ab505939bc3b3a1fc79b81668e65465c1424ab6f..8eceb6e59cba3e9db547d4e4ff6831fb11e1593c 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 e3cf88b4fd76cac1c528afa096b97e282b7b7f42..76c77025ece1a874a091891558b78ecb4d43b3d3 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 02e6cbca49d814072c598952548ef3f4fb1207f1..59fb0a9a8533bf8eed49d6b3435838737ed1f5b4 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 089651c7278b0c35f75884e617ef6e5aabe67551..feb08520326a210268a54271accfe0e57a0bfa78 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 bd4bccfc9d460c22d35fea5bfde828d1d75353fa..5aae3720d3064d20d2e917d65d70bbfbd4c67717 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 f1890595ea041cbd1e931c3859594ecef86f3be4..e1205a9b001eea69868b4fdacf47dac4df118f86 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 dcd968a0b6560eb719aa7d8f9e5c1b5501eeb536..6077788583cf5c61ae1ac98c33e33cb6016afe44 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 34fb787680d8ead35ffa30a1f3b056b7358dd560..ad1274c7f8464ae98131364bb9fe9fc602b2c50e 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 57f18673bfa92a53491ad56e17575a2874775698..7bfe6cb7f2e7a0846c2e7f71325f0bf09a0c15dc 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;