Repair breakage of rules containing INSERT ... SELECT actions, per bug

report from Joel Burton.  Turns out that my simple idea of turning the
SELECT into a subquery does not interact well *at all* with the way the
rule rewriter works.  Really what we need to make INSERT ... SELECT work
cleanly is to decouple targetlists from rangetables: an INSERT ... SELECT
wants to have two levels of targetlist but only one rangetable.  No time
for that for 7.1, however, so I've inserted some ugly hacks to make the
rewriter know explicitly about the structure of INSERT ... SELECT queries.
Ugh :-(
This commit is contained in:
Tom Lane 2000-12-05 19:15:10 +00:00
parent d9466046c0
commit a51f004d29
5 changed files with 268 additions and 212 deletions

View File

@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: analyze.c,v 1.168 2000/11/24 20:16:39 petere Exp $
* $Id: analyze.c,v 1.169 2000/12/05 19:15:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -312,7 +312,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
*/
if (stmt->selectStmt)
{
List *selectList;
ParseState *sub_pstate = make_parsestate(pstate->parentParseState);
Query *selectQuery;
RangeTblEntry *rte;
RangeTblRef *rtr;
@ -324,11 +324,18 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
* otherwise the behavior of SELECT within INSERT might be different
* from a stand-alone SELECT. (Indeed, Postgres up through 6.5 had
* bugs of just that nature...)
*
* If a non-nil rangetable was passed in, pass it down to the SELECT.
* This can only happen if we are inside a CREATE RULE, and in that
* case we want the rule's OLD and NEW rtable entries to appear as
* part of the SELECT's rtable, not as outer references for it.
*/
selectList = parse_analyze(stmt->selectStmt, pstate);
Assert(length(selectList) == 1);
sub_pstate->p_rtable = pstate->p_rtable;
pstate->p_rtable = NIL;
selectQuery = transformStmt(sub_pstate, stmt->selectStmt);
release_pstate_resources(sub_pstate);
pfree(sub_pstate);
selectQuery = (Query *) lfirst(selectList);
Assert(IsA(selectQuery, Query));
Assert(selectQuery->commandType == CMD_SELECT);
if (selectQuery->into || selectQuery->isPortal)
@ -1587,7 +1594,8 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt)
foreach(actions, stmt->actions)
{
ParseState *sub_pstate = make_parsestate(pstate->parentParseState);
Query *sub_qry;
Query *sub_qry,
*top_subqry;
bool has_old,
has_new;
@ -1608,7 +1616,14 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt)
newrte->checkForRead = false;
/* Transform the rule action statement */
sub_qry = transformStmt(sub_pstate, lfirst(actions));
top_subqry = transformStmt(sub_pstate, lfirst(actions));
/*
* If the action is INSERT...SELECT, OLD/NEW have been pushed
* down into the SELECT, and that's what we need to look at.
* (Ugly kluge ... try to fix this when we redesign querytrees.)
*/
sub_qry = getInsertSelectQuery(top_subqry, NULL);
/*
* Validate action's use of OLD/NEW, qual too
@ -1648,15 +1663,28 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt)
/*
* For efficiency's sake, add OLD to the rule action's jointree
* only if it was actually referenced in the statement or qual.
* NEW is not really a relation and should never be added.
*
* For INSERT, NEW is not really a relation (only a reference to
* the to-be-inserted tuple) and should never be added to the
* jointree.
*
* For UPDATE, we treat NEW as being another kind of reference to
* OLD, because it represents references to *transformed* tuples
* of the existing relation. It would be wrong to enter NEW
* separately in the jointree, since that would cause a double
* join of the updated relation. It's also wrong to fail to make
* a jointree entry if only NEW and not OLD is mentioned.
*/
if (has_old)
if (has_old || (has_new && stmt->event == CMD_UPDATE))
{
/* hack so we can use addRTEtoJoinList() */
sub_pstate->p_rtable = sub_qry->rtable;
sub_pstate->p_joinlist = sub_qry->jointree->fromlist;
addRTEtoJoinList(sub_pstate, oldrte);
sub_qry->jointree->fromlist = sub_pstate->p_joinlist;
}
lfirst(actions) = sub_qry;
lfirst(actions) = top_subqry;
release_pstate_resources(sub_pstate);
pfree(sub_pstate);

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteDefine.c,v 1.55 2000/10/26 21:36:50 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteDefine.c,v 1.56 2000/12/05 19:15:09 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -24,6 +24,7 @@
#include "optimizer/clauses.h"
#include "parser/parse_relation.h"
#include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteManip.h"
#include "rewrite/rewriteSupport.h"
#include "storage/smgr.h"
#include "utils/builtins.h"
@ -205,16 +206,17 @@ DefineQueryRewrite(RuleStmt *stmt)
foreach(l, action)
{
query = (Query *) lfirst(l);
if (query->resultRelation == 0)
continue;
/* Don't be fooled by INSERT/SELECT */
if (query != getInsertSelectQuery(query, NULL))
continue;
if (query->resultRelation == PRS2_OLD_VARNO)
{
elog(ERROR, "rule actions on OLD currently not supported"
"\n\tuse views or triggers instead");
}
if (query->resultRelation == PRS2_NEW_VARNO)
{
elog(ERROR, "rule actions on NEW currently not supported"
"\n\tuse triggers instead");
}
}
/*

View File

@ -7,7 +7,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.83 2000/11/08 22:09:59 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.84 2000/12/05 19:15:09 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -61,6 +61,8 @@ gatherRewriteMeta(Query *parsetree,
bool instead_flag)
{
RewriteInfo *info;
Query *sub_action;
Query **sub_action_ptr;
int rt_length;
info = (RewriteInfo *) palloc(sizeof(RewriteInfo));
@ -70,85 +72,135 @@ gatherRewriteMeta(Query *parsetree,
info->rule_action = (Query *) copyObject(rule_action);
info->rule_qual = (Node *) copyObject(rule_qual);
if (info->rule_action == NULL)
info->nothing = TRUE;
else
{
info->nothing = FALSE;
info->action = info->rule_action->commandType;
info->current_varno = rt_index;
rt_length = length(parsetree->rtable);
/* Adjust rule action and qual to offset its varnos */
info->new_varno = PRS2_NEW_VARNO + rt_length;
OffsetVarNodes((Node *) info->rule_action, rt_length, 0);
OffsetVarNodes(info->rule_qual, rt_length, 0);
/* but its references to *OLD* should point at original rt_index */
ChangeVarNodes((Node *) info->rule_action,
PRS2_OLD_VARNO + rt_length, rt_index, 0);
ChangeVarNodes(info->rule_qual,
PRS2_OLD_VARNO + rt_length, rt_index, 0);
/*
* We want the main parsetree's rtable to end up as the concatenation
* of its original contents plus those of all the relevant rule
* actions. Also store same into all the rule_action rtables.
* Some of the entries may be unused after we finish rewriting, but
* if we tried to clean those out we'd have a much harder job to
* adjust RT indexes in the query's Vars. It's OK to have unused
* RT entries, since planner will ignore them.
*
* NOTE KLUGY HACK: we assume the parsetree rtable had at least one
* entry to begin with (OK enough, else where'd the rule come from?).
* Because of this, if multiple rules nconc() their rtable additions
* onto parsetree->rtable, they'll all see the same rtable because
* they all have the same list head pointer.
*/
parsetree->rtable = nconc(parsetree->rtable,
info->rule_action->rtable);
info->rule_action->rtable = parsetree->rtable;
/*
* Each rule action's jointree should be the main parsetree's jointree
* plus that rule's jointree, but *without* the original rtindex
* that we're replacing (if present, which it won't be for INSERT).
* Note that if the rule refers to OLD, its jointree will add back
* a reference to rt_index.
*/
{
bool found;
List *newjointree = adjustJoinTreeList(parsetree,
rt_index,
&found);
info->rule_action->jointree->fromlist =
nconc(newjointree,
info->rule_action->jointree->fromlist);
}
/*
* bug here about replace CURRENT -- sort of replace current is
* deprecated now so this code shouldn't really need to be so
* clutzy but.....
*/
if (info->action != CMD_SELECT)
{ /* i.e update XXXXX */
int result_reln;
int new_result_reln;
result_reln = info->rule_action->resultRelation;
switch (result_reln)
{
case PRS2_OLD_VARNO:
new_result_reln = rt_index;
break;
case PRS2_NEW_VARNO: /* XXX */
default:
new_result_reln = result_reln + rt_length;
break;
}
info->rule_action->resultRelation = new_result_reln;
}
info->nothing = TRUE;
return info;
}
info->nothing = FALSE;
info->action = info->rule_action->commandType;
info->current_varno = rt_index;
rt_length = length(parsetree->rtable);
info->new_varno = PRS2_NEW_VARNO + rt_length;
/*
* Adjust rule action and qual to offset its varnos, so that we can
* merge its rtable into the main parsetree's rtable.
*
* If the rule action is an INSERT...SELECT, the OLD/NEW rtable
* entries will be in the SELECT part, and we have to modify that
* rather than the top-level INSERT (kluge!).
*/
sub_action = getInsertSelectQuery(info->rule_action, &sub_action_ptr);
OffsetVarNodes((Node *) sub_action, rt_length, 0);
OffsetVarNodes(info->rule_qual, rt_length, 0);
/* but references to *OLD* should point at original rt_index */
ChangeVarNodes((Node *) sub_action,
PRS2_OLD_VARNO + rt_length, rt_index, 0);
ChangeVarNodes(info->rule_qual,
PRS2_OLD_VARNO + rt_length, rt_index, 0);
/*
* Update resultRelation too ... perhaps this should be done by
* Offset/ChangeVarNodes?
*/
if (sub_action->resultRelation)
{
int result_reln;
int new_result_reln;
result_reln = sub_action->resultRelation;
switch (result_reln)
{
case PRS2_OLD_VARNO:
new_result_reln = rt_index;
break;
case PRS2_NEW_VARNO:
default:
new_result_reln = result_reln + rt_length;
break;
}
sub_action->resultRelation = new_result_reln;
}
/*
* We want the main parsetree's rtable to end up as the concatenation
* of its original contents plus those of all the relevant rule
* actions. Also store same into all the rule_action rtables.
* Some of the entries may be unused after we finish rewriting, but
* if we tried to clean those out we'd have a much harder job to
* adjust RT indexes in the query's Vars. It's OK to have unused
* RT entries, since planner will ignore them.
*
* NOTE KLUGY HACK: we assume the parsetree rtable had at least one
* entry to begin with (OK enough, else where'd the rule come from?).
* Because of this, if multiple rules nconc() their rtable additions
* onto parsetree->rtable, they'll all see the same rtable because
* they all have the same list head pointer.
*/
parsetree->rtable = nconc(parsetree->rtable,
sub_action->rtable);
sub_action->rtable = parsetree->rtable;
/*
* Each rule action's jointree should be the main parsetree's jointree
* plus that rule's jointree, but *without* the original rtindex
* that we're replacing (if present, which it won't be for INSERT).
* Note that if the rule refers to OLD, its jointree will add back
* a reference to rt_index.
*/
{
bool found;
List *newjointree = adjustJoinTreeList(parsetree,
rt_index,
&found);
sub_action->jointree->fromlist =
nconc(newjointree, sub_action->jointree->fromlist);
}
/*
* We copy the qualifications of the parsetree to the action and vice
* versa. So force hasSubLinks if one of them has it. If this is not
* right, the flag will get cleared later, but we mustn't risk having
* it not set when it needs to be.
*/
if (parsetree->hasSubLinks)
sub_action->hasSubLinks = TRUE;
else if (sub_action->hasSubLinks)
parsetree->hasSubLinks = TRUE;
/*
* Event Qualification forces copying of parsetree and
* splitting into two queries one w/rule_qual, one w/NOT
* rule_qual. Also add user query qual onto rule action
*/
AddQual(sub_action, info->rule_qual);
AddQual(sub_action, parsetree->jointree->quals);
/*
* Rewrite new.attribute w/ right hand side of target-list
* entry for appropriate field name in insert/update.
*
* KLUGE ALERT: since ResolveNew returns a mutated copy, we can't just
* apply it to sub_action; we have to remember to update the sublink
* inside info->rule_action, too.
*/
if (info->event == CMD_INSERT || info->event == CMD_UPDATE)
{
sub_action = (Query *) ResolveNew((Node *) sub_action,
info->new_varno,
0,
parsetree->targetList,
info->event,
info->current_varno);
if (sub_action_ptr)
*sub_action_ptr = sub_action;
else
info->rule_action = sub_action;
}
return info;
}
@ -536,42 +588,37 @@ orderRules(List *locks)
}
/*
* Modify the given query by adding 'AND NOT rule_qual' to its qualification.
* This is used to generate suitable "else clauses" for conditional INSTEAD
* rules.
*
* The rule_qual may contain references to OLD or NEW. OLD references are
* replaced by references to the specified rt_index (the relation that the
* rule applies to). NEW references are only possible for INSERT and UPDATE
* queries on the relation itself, and so they should be replaced by copies
* of the related entries in the query's own targetlist.
*/
static Query *
CopyAndAddQual(Query *parsetree,
List *actions,
Node *rule_qual,
int rt_index,
CmdType event)
{
Query *new_tree = (Query *) copyObject(parsetree);
Node *new_qual = NULL;
Query *rule_action = NULL;
Node *new_qual = (Node *) copyObject(rule_qual);
if (actions)
rule_action = lfirst(actions);
if (rule_qual != NULL)
new_qual = (Node *) copyObject(rule_qual);
if (rule_action != NULL)
{
List *rtable;
int rt_length;
List *jointreelist;
rtable = new_tree->rtable;
rt_length = length(rtable);
rtable = nconc(rtable, copyObject(rule_action->rtable));
new_tree->rtable = rtable;
OffsetVarNodes(new_qual, rt_length, 0);
ChangeVarNodes(new_qual, PRS2_OLD_VARNO + rt_length, rt_index, 0);
jointreelist = copyObject(rule_action->jointree->fromlist);
OffsetVarNodes((Node *) jointreelist, rt_length, 0);
ChangeVarNodes((Node *) jointreelist, PRS2_OLD_VARNO + rt_length,
rt_index, 0);
new_tree->jointree->fromlist = nconc(new_tree->jointree->fromlist,
jointreelist);
}
/* XXX -- where current doesn't work for instead nothing.... yet */
/* Fix references to OLD */
ChangeVarNodes(new_qual, PRS2_OLD_VARNO, rt_index, 0);
/* Fix references to NEW */
if (event == CMD_INSERT || event == CMD_UPDATE)
new_qual = ResolveNew(new_qual,
PRS2_NEW_VARNO,
0,
parsetree->targetList,
event,
rt_index);
/* And attach the fixed qual */
AddNotQual(new_tree, new_qual);
return new_tree;
@ -598,7 +645,6 @@ fireRules(Query *parsetree,
List *locks,
List **qual_products)
{
RewriteInfo *info;
List *results = NIL;
List *i;
@ -623,7 +669,6 @@ fireRules(Query *parsetree,
if (event_qual != NULL && *instead_flag)
{
Query *qual_product;
RewriteInfo qual_info;
/* ----------
* If there are instead rules with qualifications,
@ -642,60 +687,23 @@ fireRules(Query *parsetree,
else
qual_product = (Query *) lfirst(*qual_products);
MemSet(&qual_info, 0, sizeof(qual_info));
qual_info.event = qual_product->commandType;
qual_info.current_varno = rt_index;
qual_info.new_varno = length(qual_product->rtable) + 2;
qual_product = CopyAndAddQual(qual_product,
actions,
event_qual,
rt_index,
event);
qual_info.rule_action = qual_product;
if (event == CMD_INSERT || event == CMD_UPDATE)
FixNew(&qual_info, qual_product);
*qual_products = makeList1(qual_product);
}
foreach(r, actions)
{
Query *rule_action = lfirst(r);
Node *rule_qual = copyObject(event_qual);
RewriteInfo *info;
if (rule_action->commandType == CMD_NOTHING)
continue;
/*--------------------------------------------------
* We copy the qualifications of the parsetree
* to the action and vice versa. So force
* hasSubLinks if one of them has it.
*
* As of 6.4 only parsetree qualifications can
* have sublinks. If this changes, we must make
* this a node lookup at the end of rewriting.
*
* Jan
*--------------------------------------------------
*/
if (parsetree->hasSubLinks && !rule_action->hasSubLinks)
{
rule_action = copyObject(rule_action);
rule_action->hasSubLinks = TRUE;
}
if (!parsetree->hasSubLinks && rule_action->hasSubLinks)
parsetree->hasSubLinks = TRUE;
/*--------------------------------------------------
* Step 1:
* Rewrite current.attribute or current to tuple variable
* this appears to be done in parser?
*--------------------------------------------------
*/
info = gatherRewriteMeta(parsetree, rule_action, rule_qual,
info = gatherRewriteMeta(parsetree, rule_action, event_qual,
rt_index, event, *instead_flag);
/* handle escapable cases, or those handled by other code */
@ -707,34 +715,6 @@ fireRules(Query *parsetree,
continue;
}
if (info->action == info->event &&
info->event == CMD_SELECT)
continue;
/*
* Event Qualification forces copying of parsetree and
* splitting into two queries one w/rule_qual, one w/NOT
* rule_qual. Also add user query qual onto rule action
*/
AddQual(info->rule_action, info->rule_qual);
AddQual(info->rule_action, parsetree->jointree->quals);
/*--------------------------------------------------
* Step 2:
* Rewrite new.attribute w/ right hand side of target-list
* entry for appropriate field name in insert/update
*--------------------------------------------------
*/
if ((info->event == CMD_INSERT) || (info->event == CMD_UPDATE))
FixNew(info, parsetree);
/*--------------------------------------------------
* Step 3:
* Simplify? hey, no algorithm for simplification... let
* the planner do it.
*--------------------------------------------------
*/
results = lappend(results, info->rule_action);
pfree(info);

View File

@ -7,7 +7,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteManip.c,v 1.51 2000/11/16 22:30:29 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteManip.c,v 1.52 2000/12/05 19:15:09 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -471,6 +471,70 @@ attribute_used(Node *node, int rt_index, int attno, int sublevels_up)
}
/*
* If the given Query is an INSERT ... SELECT construct, extract and
* return the sub-Query node that represents the SELECT part. Otherwise
* return the given Query.
*
* If subquery_ptr is not NULL, then *subquery_ptr is set to the location
* of the link to the SELECT subquery inside parsetree, or NULL if not an
* INSERT ... SELECT.
*
* This is a hack needed because transformations on INSERT ... SELECTs that
* appear in rule actions should be applied to the source SELECT, not to the
* INSERT part. Perhaps this can be cleaned up with redesigned querytrees.
*/
Query *
getInsertSelectQuery(Query *parsetree, Query ***subquery_ptr)
{
Query *selectquery;
RangeTblEntry *selectrte;
RangeTblRef *rtr;
if (subquery_ptr)
*subquery_ptr = NULL;
if (parsetree == NULL)
return parsetree;
if (parsetree->commandType != CMD_INSERT)
return parsetree;
/*
* Currently, this is ONLY applied to rule-action queries, and so
* we expect to find the *OLD* and *NEW* placeholder entries in the
* given query. If they're not there, it must be an INSERT/SELECT
* in which they've been pushed down to the SELECT.
*/
if (length(parsetree->rtable) >= 2 &&
strcmp(rt_fetch(PRS2_OLD_VARNO, parsetree->rtable)->eref->relname,
"*OLD*") == 0 &&
strcmp(rt_fetch(PRS2_NEW_VARNO, parsetree->rtable)->eref->relname,
"*NEW*") == 0)
return parsetree;
Assert(parsetree->jointree && IsA(parsetree->jointree, FromExpr));
if (length(parsetree->jointree->fromlist) != 1)
elog(ERROR, "getInsertSelectQuery: expected to find SELECT subquery");
rtr = (RangeTblRef *) lfirst(parsetree->jointree->fromlist);
Assert(IsA(rtr, RangeTblRef));
selectrte = rt_fetch(rtr->rtindex, parsetree->rtable);
selectquery = selectrte->subquery;
if (! (selectquery && IsA(selectquery, Query) &&
selectquery->commandType == CMD_SELECT))
elog(ERROR, "getInsertSelectQuery: expected to find SELECT subquery");
if (length(selectquery->rtable) >= 2 &&
strcmp(rt_fetch(PRS2_OLD_VARNO, selectquery->rtable)->eref->relname,
"*OLD*") == 0 &&
strcmp(rt_fetch(PRS2_NEW_VARNO, selectquery->rtable)->eref->relname,
"*NEW*") == 0)
{
if (subquery_ptr)
*subquery_ptr = & (selectrte->subquery);
return selectquery;
}
elog(ERROR, "getInsertSelectQuery: can't find rule placeholders");
return NULL; /* not reached */
}
/*
* Add the given qualifier condition to the query's WHERE clause
*/
@ -721,25 +785,6 @@ ResolveNew(Node *node, int target_varno, int sublevels_up,
return ResolveNew_mutator(node, &context);
}
/*
* Alternate interface to ResolveNew: substitute Vars in info->rule_action
* with targetlist items from the parsetree's targetlist.
*/
void
FixNew(RewriteInfo *info, Query *parsetree)
{
ResolveNew_context context;
context.target_varno = info->new_varno;
context.sublevels_up = 0;
context.targetlist = parsetree->targetList;
context.event = info->event;
context.update_varno = info->current_varno;
query_tree_mutator(info->rule_action, ResolveNew_mutator,
(void *) &context, true);
}
#ifdef NOT_USED

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: rewriteManip.h,v 1.23 2000/09/29 18:21:24 tgl Exp $
* $Id: rewriteManip.h,v 1.24 2000/12/05 19:15:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -28,6 +28,8 @@ extern bool rangeTableEntry_used(Node *node, int rt_index,
extern bool attribute_used(Node *node, int rt_index, int attno,
int sublevels_up);
extern Query *getInsertSelectQuery(Query *parsetree, Query ***subquery_ptr);
extern void AddQual(Query *parsetree, Node *qual);
extern void AddHavingQual(Query *parsetree, Node *havingQual);
extern void AddNotQual(Query *parsetree, Node *qual);
@ -37,6 +39,5 @@ extern bool checkExprHasSubLink(Node *node);
extern Node *ResolveNew(Node *node, int target_varno, int sublevels_up,
List *targetlist, int event, int update_varno);
extern void FixNew(RewriteInfo *info, Query *parsetree);
#endif /* REWRITEMANIP_H */