diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 951b63b5b4..c3039c8167 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -25,6 +25,8 @@ ALTER TABLE [ IF EXISTS ] [ ONLY ] nameaction [, ... ] ALTER TABLE [ IF EXISTS ] [ ONLY ] name [ * ] RENAME [ COLUMN ] column TO new_column +ALTER TABLE [ IF EXISTS ] [ ONLY ] name [ * ] + RENAME CONSTRAINT constraint_name TO new_constraint_name ALTER TABLE [ IF EXISTS ] name RENAME TO new_name ALTER TABLE [ IF EXISTS ] name @@ -569,8 +571,8 @@ ALTER TABLE [ IF EXISTS ] name The RENAME forms change the name of a table - (or an index, sequence, or view) or the name of an individual column in - a table. There is no effect on the stored data. + (or an index, sequence, or view), the name of an individual column in + a table, or the name of a constraint of the table. There is no effect on the stored data. @@ -883,7 +885,8 @@ ALTER TABLE [ IF EXISTS ] name 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 + rename, or change the type of a column, or rename an inherited constraint + in the parent table without doing the same to the descendants. That is, ALTER TABLE ONLY will be rejected. This ensures that the descendants always have columns matching the parent. @@ -982,6 +985,13 @@ ALTER TABLE distributors RENAME TO suppliers; + + To rename an existing constraint: + +ALTER TABLE distributors RENAME CONSTRAINT zipchk TO zip_check; + + + To add a not-null constraint to a column: diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c index 9175405af2..4dd9927afb 100644 --- a/src/backend/commands/alter.c +++ b/src/backend/commands/alter.c @@ -57,6 +57,10 @@ ExecRenameStmt(RenameStmt *stmt) RenameCollation(stmt->object, stmt->newname); break; + case OBJECT_CONSTRAINT: + RenameConstraint(stmt); + break; + case OBJECT_CONVERSION: RenameConversion(stmt->object, stmt->newname); break; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 25ca356b86..9615380f05 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -2327,6 +2327,108 @@ renameatt(RenameStmt *stmt) stmt->behavior); } + +/* + * same logic as renameatt_internal + */ +static void +rename_constraint_internal(Oid myrelid, + const char *oldconname, + const char *newconname, + bool recurse, + bool recursing, + int expected_parents) +{ + Relation targetrelation; + Oid constraintOid; + HeapTuple tuple; + Form_pg_constraint con; + + targetrelation = relation_open(myrelid, AccessExclusiveLock); + /* don't tell it whether we're recursing; we allow changing typed tables here */ + renameatt_check(myrelid, RelationGetForm(targetrelation), false); + + constraintOid = get_constraint_oid(myrelid, oldconname, false); + + tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constraintOid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for constraint %u", + constraintOid); + con = (Form_pg_constraint) GETSTRUCT(tuple); + + if (con->contype == CONSTRAINT_CHECK && !con->conisonly) + { + if (recurse) + { + List *child_oids, + *child_numparents; + ListCell *lo, + *li; + + child_oids = find_all_inheritors(myrelid, AccessExclusiveLock, + &child_numparents); + + forboth(lo, child_oids, li, child_numparents) + { + Oid childrelid = lfirst_oid(lo); + int numparents = lfirst_int(li); + + if (childrelid == myrelid) + continue; + + rename_constraint_internal(childrelid, oldconname, newconname, false, true, numparents); + } + } + else + { + if (expected_parents == 0 && + find_inheritance_children(myrelid, NoLock) != NIL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("inherited constraint \"%s\" must be renamed in child tables too", + oldconname))); + } + + if (con->coninhcount > expected_parents) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot rename inherited constraint \"%s\"", + oldconname))); + } + + if (con->conindid + && (con->contype == CONSTRAINT_PRIMARY + || con->contype == CONSTRAINT_UNIQUE + || con->contype == CONSTRAINT_EXCLUSION)) + /* rename the index; this renames the constraint as well */ + RenameRelationInternal(con->conindid, newconname); + else + RenameConstraintById(constraintOid, newconname); + + ReleaseSysCache(tuple); + + relation_close(targetrelation, NoLock); /* close rel but keep lock */ +} + +void +RenameConstraint(RenameStmt *stmt) +{ + Oid relid; + + /* lock level taken here should match rename_constraint_internal */ + relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock, + false, false, + RangeVarCallbackForRenameAttribute, + NULL); + + rename_constraint_internal(relid, + stmt->subname, + stmt->newname, + interpretInhOption(stmt->relation->inhOpt), /* recursive? */ + false, /* recursing? */ + 0 /* expected inhcount */); +} + /* * Execute ALTER TABLE/INDEX/SEQUENCE/VIEW/FOREIGN TABLE RENAME */ diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 9aea2cd80b..feb28a4172 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -6731,6 +6731,16 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name n->missing_ok = true; $$ = (Node *)n; } + | ALTER TABLE relation_expr RENAME CONSTRAINT name TO name + { + RenameStmt *n = makeNode(RenameStmt); + n->renameType = OBJECT_CONSTRAINT; + n->relationType = OBJECT_TABLE; + n->relation = $3; + n->subname = $6; + n->newname = $8; + $$ = (Node *)n; + } | ALTER FOREIGN TABLE relation_expr RENAME opt_column name TO name { RenameStmt *n = makeNode(RenameStmt); diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h index 03f397de63..47b0cddc9b 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -48,6 +48,8 @@ extern void SetRelationHasSubclass(Oid relationId, bool relhassubclass); extern void renameatt(RenameStmt *stmt); +extern void RenameConstraint(RenameStmt *stmt); + extern void RenameRelation(RenameStmt *stmt); extern void RenameRelationInternal(Oid myrelid, diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index 4aba58c450..eba0493089 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -160,6 +160,141 @@ DROP VIEW tmp_view_new; -- toast-like relation name alter table stud_emp rename to pg_toast_stud_emp; alter table pg_toast_stud_emp rename to stud_emp; +-- renaming index should rename constraint as well +ALTER TABLE onek ADD CONSTRAINT onek_unique1_constraint UNIQUE (unique1); +NOTICE: ALTER TABLE / ADD UNIQUE will create implicit index "onek_unique1_constraint" for table "onek" +ALTER INDEX onek_unique1_constraint RENAME TO onek_unique1_constraint_foo; +ALTER TABLE onek DROP CONSTRAINT onek_unique1_constraint_foo; +-- renaming constraint +ALTER TABLE onek ADD CONSTRAINT onek_check_constraint CHECK (unique1 >= 0); +ALTER TABLE onek RENAME CONSTRAINT onek_check_constraint TO onek_check_constraint_foo; +ALTER TABLE onek DROP CONSTRAINT onek_check_constraint_foo; +-- renaming constraint should rename index as well +ALTER TABLE onek ADD CONSTRAINT onek_unique1_constraint UNIQUE (unique1); +NOTICE: ALTER TABLE / ADD UNIQUE will create implicit index "onek_unique1_constraint" for table "onek" +DROP INDEX onek_unique1_constraint; -- to see whether it's there +ERROR: cannot drop index onek_unique1_constraint because constraint onek_unique1_constraint on table onek requires it +HINT: You can drop constraint onek_unique1_constraint on table onek instead. +ALTER TABLE onek RENAME CONSTRAINT onek_unique1_constraint TO onek_unique1_constraint_foo; +DROP INDEX onek_unique1_constraint_foo; -- to see whether it's there +ERROR: cannot drop index onek_unique1_constraint_foo because constraint onek_unique1_constraint_foo on table onek requires it +HINT: You can drop constraint onek_unique1_constraint_foo on table onek instead. +ALTER TABLE onek DROP CONSTRAINT onek_unique1_constraint_foo; +-- renaming constraints vs. inheritance +CREATE TABLE constraint_rename_test (a int CONSTRAINT con1 CHECK (a > 0), b int, c int); +\d constraint_rename_test +Table "public.constraint_rename_test" + Column | Type | Modifiers +--------+---------+----------- + a | integer | + b | integer | + c | integer | +Check constraints: + "con1" CHECK (a > 0) + +CREATE TABLE constraint_rename_test2 (a int CONSTRAINT con1 CHECK (a > 0), d int) INHERITS (constraint_rename_test); +NOTICE: merging column "a" with inherited definition +NOTICE: merging constraint "con1" with inherited definition +\d constraint_rename_test2 +Table "public.constraint_rename_test2" + Column | Type | Modifiers +--------+---------+----------- + a | integer | + b | integer | + c | integer | + d | integer | +Check constraints: + "con1" CHECK (a > 0) +Inherits: constraint_rename_test + +ALTER TABLE constraint_rename_test2 RENAME CONSTRAINT con1 TO con1foo; -- fail +ERROR: cannot rename inherited constraint "con1" +ALTER TABLE ONLY constraint_rename_test RENAME CONSTRAINT con1 TO con1foo; -- fail +ERROR: inherited constraint "con1" must be renamed in child tables too +ALTER TABLE constraint_rename_test RENAME CONSTRAINT con1 TO con1foo; -- ok +\d constraint_rename_test +Table "public.constraint_rename_test" + Column | Type | Modifiers +--------+---------+----------- + a | integer | + b | integer | + c | integer | +Check constraints: + "con1foo" CHECK (a > 0) +Number of child tables: 1 (Use \d+ to list them.) + +\d constraint_rename_test2 +Table "public.constraint_rename_test2" + Column | Type | Modifiers +--------+---------+----------- + a | integer | + b | integer | + c | integer | + d | integer | +Check constraints: + "con1foo" CHECK (a > 0) +Inherits: constraint_rename_test + +ALTER TABLE ONLY constraint_rename_test ADD CONSTRAINT con2 CHECK (b > 0); +ALTER TABLE ONLY constraint_rename_test RENAME CONSTRAINT con2 TO con2foo; -- ok +ALTER TABLE constraint_rename_test RENAME CONSTRAINT con2foo TO con2bar; -- ok +\d constraint_rename_test +Table "public.constraint_rename_test" + Column | Type | Modifiers +--------+---------+----------- + a | integer | + b | integer | + c | integer | +Check constraints: + "con2bar" (ONLY) CHECK (b > 0) + "con1foo" CHECK (a > 0) +Number of child tables: 1 (Use \d+ to list them.) + +\d constraint_rename_test2 +Table "public.constraint_rename_test2" + Column | Type | Modifiers +--------+---------+----------- + a | integer | + b | integer | + c | integer | + d | integer | +Check constraints: + "con1foo" CHECK (a > 0) +Inherits: constraint_rename_test + +ALTER TABLE constraint_rename_test ADD CONSTRAINT con3 PRIMARY KEY (a); +NOTICE: ALTER TABLE / ADD PRIMARY KEY will create implicit index "con3" for table "constraint_rename_test" +ALTER TABLE constraint_rename_test RENAME CONSTRAINT con3 TO con3foo; -- ok +\d constraint_rename_test +Table "public.constraint_rename_test" + Column | Type | Modifiers +--------+---------+----------- + a | integer | not null + b | integer | + c | integer | +Indexes: + "con3foo" PRIMARY KEY, btree (a) +Check constraints: + "con2bar" (ONLY) CHECK (b > 0) + "con1foo" CHECK (a > 0) +Number of child tables: 1 (Use \d+ to list them.) + +\d constraint_rename_test2 +Table "public.constraint_rename_test2" + Column | Type | Modifiers +--------+---------+----------- + a | integer | + b | integer | + c | integer | + d | integer | +Check constraints: + "con1foo" CHECK (a > 0) +Inherits: constraint_rename_test + +DROP TABLE constraint_rename_test2; +DROP TABLE constraint_rename_test; +ALTER TABLE IF EXISTS constraint_rename_test ADD CONSTRAINT con4 UNIQUE (a); +NOTICE: relation "constraint_rename_test" does not exist, skipping -- FOREIGN KEY CONSTRAINT adding TEST CREATE TABLE tmp2 (a int primary key); NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "tmp2_pkey" for table "tmp2" diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index d4e4c4958d..50c58d23e1 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -191,6 +191,46 @@ DROP VIEW tmp_view_new; alter table stud_emp rename to pg_toast_stud_emp; alter table pg_toast_stud_emp rename to stud_emp; +-- renaming index should rename constraint as well +ALTER TABLE onek ADD CONSTRAINT onek_unique1_constraint UNIQUE (unique1); +ALTER INDEX onek_unique1_constraint RENAME TO onek_unique1_constraint_foo; +ALTER TABLE onek DROP CONSTRAINT onek_unique1_constraint_foo; + +-- renaming constraint +ALTER TABLE onek ADD CONSTRAINT onek_check_constraint CHECK (unique1 >= 0); +ALTER TABLE onek RENAME CONSTRAINT onek_check_constraint TO onek_check_constraint_foo; +ALTER TABLE onek DROP CONSTRAINT onek_check_constraint_foo; + +-- renaming constraint should rename index as well +ALTER TABLE onek ADD CONSTRAINT onek_unique1_constraint UNIQUE (unique1); +DROP INDEX onek_unique1_constraint; -- to see whether it's there +ALTER TABLE onek RENAME CONSTRAINT onek_unique1_constraint TO onek_unique1_constraint_foo; +DROP INDEX onek_unique1_constraint_foo; -- to see whether it's there +ALTER TABLE onek DROP CONSTRAINT onek_unique1_constraint_foo; + +-- renaming constraints vs. inheritance +CREATE TABLE constraint_rename_test (a int CONSTRAINT con1 CHECK (a > 0), b int, c int); +\d constraint_rename_test +CREATE TABLE constraint_rename_test2 (a int CONSTRAINT con1 CHECK (a > 0), d int) INHERITS (constraint_rename_test); +\d constraint_rename_test2 +ALTER TABLE constraint_rename_test2 RENAME CONSTRAINT con1 TO con1foo; -- fail +ALTER TABLE ONLY constraint_rename_test RENAME CONSTRAINT con1 TO con1foo; -- fail +ALTER TABLE constraint_rename_test RENAME CONSTRAINT con1 TO con1foo; -- ok +\d constraint_rename_test +\d constraint_rename_test2 +ALTER TABLE ONLY constraint_rename_test ADD CONSTRAINT con2 CHECK (b > 0); +ALTER TABLE ONLY constraint_rename_test RENAME CONSTRAINT con2 TO con2foo; -- ok +ALTER TABLE constraint_rename_test RENAME CONSTRAINT con2foo TO con2bar; -- ok +\d constraint_rename_test +\d constraint_rename_test2 +ALTER TABLE constraint_rename_test ADD CONSTRAINT con3 PRIMARY KEY (a); +ALTER TABLE constraint_rename_test RENAME CONSTRAINT con3 TO con3foo; -- ok +\d constraint_rename_test +\d constraint_rename_test2 +DROP TABLE constraint_rename_test2; +DROP TABLE constraint_rename_test; +ALTER TABLE IF EXISTS constraint_rename_test ADD CONSTRAINT con4 UNIQUE (a); + -- FOREIGN KEY CONSTRAINT adding TEST CREATE TABLE tmp2 (a int primary key);