diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 5e44a64c01246a0715ebc5a4eda4723150def204..4d5adc4d47bc45334041c8cbacb907486f3d544e 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -14,7 +14,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepunion.c,v 1.83 2002/12/14 00:17:57 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepunion.c,v 1.84 2003/01/05 00:56:40 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -67,6 +67,7 @@ static List *generate_append_tlist(List *colTypes, bool flag, List *refnames_tlist); static Node *adjust_inherited_attrs_mutator(Node *node, adjust_inherited_attrs_context *context); +static List *adjust_inherited_tlist(List *tlist, Oid new_relid); /* @@ -768,10 +769,17 @@ adjust_inherited_attrs(Node *node, Query *newnode; FLATCOPY(newnode, query, Query); - if (newnode->resultRelation == old_rt_index) - newnode->resultRelation = new_rt_index; query_tree_mutator(newnode, adjust_inherited_attrs_mutator, (void *) &context, QTW_IGNORE_SUBQUERIES); + if (newnode->resultRelation == old_rt_index) + { + newnode->resultRelation = new_rt_index; + /* Fix tlist resnos too, if it's inherited UPDATE */ + if (newnode->commandType == CMD_UPDATE) + newnode->targetList = + adjust_inherited_tlist(newnode->targetList, + new_relid); + } return (Node *) newnode; } else @@ -887,3 +895,101 @@ adjust_inherited_attrs_mutator(Node *node, return expression_tree_mutator(node, adjust_inherited_attrs_mutator, (void *) context); } + +/* + * Adjust the targetlist entries of an inherited UPDATE operation + * + * The expressions have already been fixed, but we have to make sure that + * the target resnos match the child table (they may not, in the case of + * a column that was added after-the-fact by ALTER TABLE). In some cases + * this can force us to re-order the tlist to preserve resno ordering. + * (We do all this work in special cases so that preptlist.c is fast for + * the typical case.) + * + * The given tlist has already been through expression_tree_mutator; + * therefore the TargetEntry nodes are fresh copies that it's okay to + * scribble on. But the Resdom nodes have not been copied; make new ones + * if we need to change them! + * + * Note that this is not needed for INSERT because INSERT isn't inheritable. + */ +static List * +adjust_inherited_tlist(List *tlist, Oid new_relid) +{ + bool changed_it = false; + List *tl; + List *new_tlist; + bool more; + int attrno; + + /* Scan tlist and update resnos to match attnums of new_relid */ + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + Resdom *resdom = tle->resdom; + + if (resdom->resjunk) + continue; /* ignore junk items */ + + attrno = get_attnum(new_relid, resdom->resname); + if (attrno == InvalidAttrNumber) + elog(ERROR, "Relation \"%s\" has no column \"%s\"", + get_rel_name(new_relid), resdom->resname); + if (resdom->resno != attrno) + { + resdom = (Resdom *) copyObject((Node *) resdom); + resdom->resno = attrno; + tle->resdom = resdom; + changed_it = true; + } + } + + /* + * If we changed anything, re-sort the tlist by resno, and make sure + * resjunk entries have resnos above the last real resno. The sort + * algorithm is a bit stupid, but for such a seldom-taken path, small + * is probably better than fast. + */ + if (!changed_it) + return tlist; + + new_tlist = NIL; + more = true; + for (attrno = 1; more; attrno++) + { + more = false; + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + Resdom *resdom = tle->resdom; + + if (resdom->resjunk) + continue; /* ignore junk items */ + + if (resdom->resno == attrno) + new_tlist = lappend(new_tlist, tle); + else if (resdom->resno > attrno) + more = true; + } + } + + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + Resdom *resdom = tle->resdom; + + if (!resdom->resjunk) + continue; /* here, ignore non-junk items */ + + if (resdom->resno != attrno) + { + resdom = (Resdom *) copyObject((Node *) resdom); + resdom->resno = attrno; + tle->resdom = resdom; + } + new_tlist = lappend(new_tlist, tle); + attrno++; + } + + return new_tlist; +} diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index e0e9b8dc5156dea34d7826c5eaf1de3e783d230b..877d8bdd84bdb1aa27789ca268a35aa5082274f1 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -1179,3 +1179,31 @@ order by relname, attnum; drop table p1, p2 cascade; NOTICE: Drop cascades to table c1 NOTICE: Drop cascades to table gc1 +-- test renumbering of child-table columns in inherited operations +create table p1 (f1 int); +create table c1 (f2 text, f3 int) inherits (p1); +alter table p1 add column a1 int check (a1 > 0); +alter table p1 add column f2 text; +NOTICE: ALTER TABLE: merging definition of column "f2" for child c1 +insert into p1 values (1,2,'abc'); +insert into c1 values(11,'xyz',33,0); -- should fail +ERROR: ExecInsert: rejected due to CHECK constraint "p1_a1" on "c1" +insert into c1 values(11,'xyz',33,22); +select * from p1; + f1 | a1 | f2 +----+----+----- + 1 | 2 | abc + 11 | 22 | xyz +(2 rows) + +update p1 set a1 = a1 + 1, f2 = upper(f2); +select * from p1; + f1 | a1 | f2 +----+----+----- + 1 | 3 | ABC + 11 | 23 | XYZ +(2 rows) + +drop table p1 cascade; +NOTICE: Drop cascades to table c1 +NOTICE: Drop cascades to constraint p1_a1 on table c1 diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index 4d20705f53a5a4b8bfdf46a55c3ba19d57d138a2..79ea952adeefb2f15e1e52181756abaa6a0ca9f9 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -849,3 +849,21 @@ where relname in ('p1','p2','c1','gc1') and attnum > 0 and not attisdropped order by relname, attnum; drop table p1, p2 cascade; + +-- test renumbering of child-table columns in inherited operations + +create table p1 (f1 int); +create table c1 (f2 text, f3 int) inherits (p1); + +alter table p1 add column a1 int check (a1 > 0); +alter table p1 add column f2 text; + +insert into p1 values (1,2,'abc'); +insert into c1 values(11,'xyz',33,0); -- should fail +insert into c1 values(11,'xyz',33,22); + +select * from p1; +update p1 set a1 = a1 + 1, f2 = upper(f2); +select * from p1; + +drop table p1 cascade;