diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 3b1d5b3976..dcd5234754 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -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); diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c index a83e020233..4eb7d7e89d 100644 --- a/src/backend/rewrite/rewriteDefine.c +++ b/src/backend/rewrite/rewriteDefine.c @@ -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"); - } } /* diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index a0a9d5671e..6b842582b4 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -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); diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c index 3e1f8f4a68..02aa2d7665 100644 --- a/src/backend/rewrite/rewriteManip.c +++ b/src/backend/rewrite/rewriteManip.c @@ -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 diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h index 2cfbf0e061..85b3a774ca 100644 --- a/src/include/rewrite/rewriteManip.h +++ b/src/include/rewrite/rewriteManip.h @@ -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 */