Commit a51f004d authored by Tom Lane's avatar Tom Lane

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 :-(
parent d9466046
......@@ -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);
......
......@@ -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");
}
}
/*
......
This diff is collapsed.
......@@ -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
......
......@@ -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 */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment