1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-16 06:01:02 +03:00

Fix ALTER TABLE / SET TYPE for irregular inheritance

If inherited tables don't have exactly the same schema, the USING clause
in an ALTER TABLE / SET DATA TYPE misbehaves when applied to the
children tables since commit 9550e8348b.  Starting with that commit,
the attribute numbers in the USING expression are fixed during parse
analysis.  This can lead to bogus errors being reported during
execution, such as:
   ERROR:  attribute 2 has wrong type
   DETAIL:  Table has type smallint, but query expects integer.

Since it wouldn't do to revert to the original coding, we now apply a
transformation to map the attribute numbers to the correct ones for each
child.

Reported by Justin Pryzby
Analysis by Tom Lane; patch by me.
Discussion: https://postgr.es/m/20170102225618.GA10071@telsasoft.com
This commit is contained in:
Alvaro Herrera
2017-01-09 19:26:58 -03:00
parent 7403561c0f
commit 3957b58b88
5 changed files with 175 additions and 49 deletions

View File

@ -20,6 +20,7 @@
#include "access/reloptions.h"
#include "access/relscan.h"
#include "access/sysattr.h"
#include "access/tupconvert.h"
#include "access/xact.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
@ -8540,12 +8541,69 @@ ATPrepAlterColumnType(List **wqueue,
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.
* Recurse manually by queueing a new command for each child, if
* necessary. We cannot apply ATSimpleRecursion here because we need to
* remap attribute numbers in the USING expression, if any.
*
* 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, lockmode);
{
Oid relid = RelationGetRelid(rel);
ListCell *child;
List *children;
children = find_all_inheritors(relid, lockmode, NULL);
/*
* 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 = lfirst_oid(child);
Relation childrel;
if (childrelid == relid)
continue;
/* find_all_inheritors already got lock */
childrel = relation_open(childrelid, NoLock);
CheckTableNotInUse(childrel, "ALTER TABLE");
/*
* Remap the attribute numbers. If no USING expression was
* specified, there is no need for this step.
*/
if (def->cooked_default)
{
AttrNumber *attmap;
bool found_whole_row;
/* create a copy to scribble on */
cmd = copyObject(cmd);
attmap = convert_tuples_by_name_map(RelationGetDescr(childrel),
RelationGetDescr(rel),
gettext_noop("could not convert row type"));
((ColumnDef *) cmd->def)->cooked_default =
map_variable_attnos(def->cooked_default,
1, 0,
attmap, RelationGetDescr(rel)->natts,
&found_whole_row);
if (found_whole_row)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert whole-row table reference"),
errdetail("USING expression contains a whole-row table reference.")));
pfree(attmap);
}
ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode);
relation_close(childrel, NoLock);
}
}
else if (!recursing &&
find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
ereport(ERROR,