mirror of
https://github.com/postgres/postgres.git
synced 2025-08-27 07:42:10 +03:00
Prevent CREATE TABLE LIKE/INHERITS from (mis) copying whole-row Vars.
If a CHECK constraint or index definition contained a whole-row Var (that is, "table.*"), an attempt to copy that definition via CREATE TABLE LIKE or table inheritance produced incorrect results: the copied Var still claimed to have the rowtype of the source table, rather than the created table. For the LIKE case, it seems reasonable to just throw error for this situation, since the point of LIKE is that the new table is not permanently coupled to the old, so there's no reason to assume its rowtype will stay compatible. In the inheritance case, we should ideally allow such constraints, but doing so will require nontrivial refactoring of CREATE TABLE processing (because we'd need to know the OID of the new table's rowtype before we adjust inherited CHECK constraints). In view of the lack of previous complaints, that doesn't seem worth the risk in a back-patched bug fix, so just make it throw error for the inheritance case as well. Along the way, replace change_varattnos_of_a_node() with a more robust function map_variable_attnos(), which is capable of being extended to handle insertion of ConvertRowtypeExpr whenever we get around to fixing the inheritance case nicely, and in the meantime it returns a failure indication to the caller so that a helpful message with some context can be thrown. Also, this code will do the right thing with subselects (if we ever allow them in CHECK or indexes), and it range-checks varattnos before using them to index into the map array. Per report from Sergey Konoplev. Back-patch to all supported branches.
This commit is contained in:
@@ -1217,6 +1217,119 @@ replace_rte_variables_mutator(Node *node,
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* map_variable_attnos() finds all user-column Vars in an expression tree
|
||||
* that reference a particular RTE, and adjusts their varattnos according
|
||||
* to the given mapping array (varattno n is replaced by attno_map[n-1]).
|
||||
* Vars for system columns are not modified.
|
||||
*
|
||||
* A zero in the mapping array represents a dropped column, which should not
|
||||
* appear in the expression.
|
||||
*
|
||||
* If the expression tree contains a whole-row Var for the target RTE,
|
||||
* the Var is not changed but *found_whole_row is returned as TRUE.
|
||||
* For most callers this is an error condition, but we leave it to the caller
|
||||
* to report the error so that useful context can be provided. (In some
|
||||
* usages it would be appropriate to modify the Var's vartype and insert a
|
||||
* ConvertRowtypeExpr node to map back to the original vartype. We might
|
||||
* someday extend this function's API to support that. For now, the only
|
||||
* concession to that future need is that this function is a tree mutator
|
||||
* not just a walker.)
|
||||
*
|
||||
* This could be built using replace_rte_variables and a callback function,
|
||||
* but since we don't ever need to insert sublinks, replace_rte_variables is
|
||||
* overly complicated.
|
||||
*/
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int target_varno; /* RTE index to search for */
|
||||
int sublevels_up; /* (current) nesting depth */
|
||||
const AttrNumber *attno_map; /* map array for user attnos */
|
||||
int map_length; /* number of entries in attno_map[] */
|
||||
bool *found_whole_row; /* output flag */
|
||||
} map_variable_attnos_context;
|
||||
|
||||
static Node *
|
||||
map_variable_attnos_mutator(Node *node,
|
||||
map_variable_attnos_context *context)
|
||||
{
|
||||
if (node == NULL)
|
||||
return NULL;
|
||||
if (IsA(node, Var))
|
||||
{
|
||||
Var *var = (Var *) node;
|
||||
|
||||
if (var->varno == context->target_varno &&
|
||||
var->varlevelsup == context->sublevels_up)
|
||||
{
|
||||
/* Found a matching variable, make the substitution */
|
||||
Var *newvar = (Var *) palloc(sizeof(Var));
|
||||
int attno = var->varattno;
|
||||
|
||||
*newvar = *var;
|
||||
if (attno > 0)
|
||||
{
|
||||
/* user-defined column, replace attno */
|
||||
if (attno > context->map_length ||
|
||||
context->attno_map[attno - 1] == 0)
|
||||
elog(ERROR, "unexpected varattno %d in expression to be mapped",
|
||||
attno);
|
||||
newvar->varattno = newvar->varoattno = context->attno_map[attno - 1];
|
||||
}
|
||||
else if (attno == 0)
|
||||
{
|
||||
/* whole-row variable, warn caller */
|
||||
*(context->found_whole_row) = true;
|
||||
}
|
||||
return (Node *) newvar;
|
||||
}
|
||||
/* otherwise fall through to copy the var normally */
|
||||
}
|
||||
else if (IsA(node, Query))
|
||||
{
|
||||
/* Recurse into RTE subquery or not-yet-planned sublink subquery */
|
||||
Query *newnode;
|
||||
|
||||
context->sublevels_up++;
|
||||
newnode = query_tree_mutator((Query *) node,
|
||||
map_variable_attnos_mutator,
|
||||
(void *) context,
|
||||
0);
|
||||
context->sublevels_up--;
|
||||
return (Node *) newnode;
|
||||
}
|
||||
return expression_tree_mutator(node, map_variable_attnos_mutator,
|
||||
(void *) context);
|
||||
}
|
||||
|
||||
Node *
|
||||
map_variable_attnos(Node *node,
|
||||
int target_varno, int sublevels_up,
|
||||
const AttrNumber *attno_map, int map_length,
|
||||
bool *found_whole_row)
|
||||
{
|
||||
map_variable_attnos_context context;
|
||||
|
||||
context.target_varno = target_varno;
|
||||
context.sublevels_up = sublevels_up;
|
||||
context.attno_map = attno_map;
|
||||
context.map_length = map_length;
|
||||
context.found_whole_row = found_whole_row;
|
||||
|
||||
*found_whole_row = false;
|
||||
|
||||
/*
|
||||
* Must be prepared to start with a Query or a bare expression tree; if
|
||||
* it's a Query, we don't want to increment sublevels_up.
|
||||
*/
|
||||
return query_or_expression_tree_mutator(node,
|
||||
map_variable_attnos_mutator,
|
||||
(void *) &context,
|
||||
0);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ResolveNew - replace Vars with corresponding items from a targetlist
|
||||
*
|
||||
|
Reference in New Issue
Block a user