X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fbackend%2Frewrite%2FrewriteHandler.c;h=3a50642fce8008d6b99b816515022cd10773d232;hb=bb742407947ad1cbf19355d24282380d576e7654;hp=e5a1d4c74725f172e826eb917f683fef9a9d8693;hpb=0f4ff460c479e9c9bff90e8208f0a5272b9925df;p=postgresql diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index e5a1d4c747..3a50642fce 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -3,28 +3,30 @@ * rewriteHandler.c * Primary module of query rewriter. * - * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.172 2007/03/17 00:11:04 tgl Exp $ + * src/backend/rewrite/rewriteHandler.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/heapam.h" +#include "access/sysattr.h" #include "catalog/pg_type.h" #include "nodes/makefuncs.h" -#include "optimizer/clauses.h" +#include "nodes/nodeFuncs.h" #include "parser/analyze.h" #include "parser/parse_coerce.h" -#include "parser/parse_expr.h" #include "parser/parsetree.h" +#include "rewrite/rewriteDefine.h" #include "rewrite/rewriteHandler.h" #include "rewrite/rewriteManip.h" #include "utils/builtins.h" #include "utils/lsyscache.h" +#include "commands/trigger.h" /* We use a list of these to detect recursion in RewriteQuery */ @@ -42,19 +44,22 @@ static Query *rewriteRuleAction(Query *parsetree, CmdType event, bool *returning_flag); static List *adjustJoinTreeList(Query *parsetree, bool removert, int rt_index); -static void rewriteTargetList(Query *parsetree, Relation target_relation, - List **attrno_list); +static void rewriteTargetListIU(Query *parsetree, Relation target_relation, + List **attrno_list); static TargetEntry *process_matched_tle(TargetEntry *src_tle, TargetEntry *prior_tle, const char *attrName); static Node *get_assignment_input(Node *node); static void rewriteValuesRTE(RangeTblEntry *rte, Relation target_relation, List *attrnos); +static void rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte, + Relation target_relation); static void markQueryForLocking(Query *qry, Node *jtnode, - bool forUpdate, bool noWait); + bool forUpdate, bool noWait, bool pushedDown); static List *matchLocks(CmdType event, RuleLock *rulelocks, int varno, Query *parsetree); -static Query *fireRIRrules(Query *parsetree, List *activeRIRs); +static Query *fireRIRrules(Query *parsetree, List *activeRIRs, + bool forUpdatePushedDown); /* @@ -63,6 +68,10 @@ static Query *fireRIRrules(Query *parsetree, List *activeRIRs); * These locks will ensure that the relation schemas don't change under us * while we are rewriting and planning the query. * + * forUpdatePushedDown indicates that a pushed-down FOR UPDATE/SHARE applies + * to the current subquery, requiring all rels to be opened with RowShareLock. + * This should always be false at the start of the recursion. + * * A secondary purpose of this routine is to fix up JOIN RTE references to * dropped columns (see details below). Because the RTEs are modified in * place, it is generally appropriate for the caller of this routine to have @@ -90,7 +99,7 @@ static Query *fireRIRrules(Query *parsetree, List *activeRIRs); * construction of a nested join was O(N^2) in the nesting depth.) */ void -AcquireRewriteLocks(Query *parsetree) +AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown) { ListCell *l; int rt_index; @@ -128,7 +137,8 @@ AcquireRewriteLocks(Query *parsetree) */ if (rt_index == parsetree->resultRelation) lockmode = RowExclusiveLock; - else if (get_rowmark(parsetree, rt_index)) + else if (forUpdatePushedDown || + get_parse_rowmark(parsetree, rt_index) != NULL) lockmode = RowShareLock; else lockmode = AccessShareLock; @@ -191,7 +201,7 @@ AcquireRewriteLocks(Query *parsetree) * now-dropped type OID, but it doesn't really * matter what type the Const claims to be. */ - aliasvar = (Var *) makeNullConst(INT4OID); + aliasvar = (Var *) makeNullConst(INT4OID, -1); } } newaliasvars = lappend(newaliasvars, aliasvar); @@ -205,7 +215,9 @@ AcquireRewriteLocks(Query *parsetree) * The subquery RTE itself is all right, but we have to * recurse to process the represented subquery. */ - AcquireRewriteLocks(rte->subquery); + AcquireRewriteLocks(rte->subquery, + (forUpdatePushedDown || + get_parse_rowmark(parsetree, rt_index) != NULL)); break; default: @@ -214,13 +226,21 @@ AcquireRewriteLocks(Query *parsetree) } } + /* Recurse into subqueries in WITH */ + foreach(l, parsetree->cteList) + { + CommonTableExpr *cte = (CommonTableExpr *) lfirst(l); + + AcquireRewriteLocks((Query *) cte->ctequery, false); + } + /* * Recurse into sublink subqueries, too. But we already did the ones in - * the rtable. + * the rtable and cteList. */ if (parsetree->hasSubLinks) query_tree_walker(parsetree, acquireLocksOnSubLinks, NULL, - QTW_IGNORE_RT_SUBQUERIES); + QTW_IGNORE_RC_SUBQUERIES); } /* @@ -236,7 +256,7 @@ acquireLocksOnSubLinks(Node *node, void *context) SubLink *sub = (SubLink *) node; /* Do what we came for */ - AcquireRewriteLocks((Query *) sub->subselect); + AcquireRewriteLocks((Query *) sub->subselect, false); /* Fall through to process lefthand args of SubLink */ } @@ -289,7 +309,7 @@ rewriteRuleAction(Query *parsetree, /* * Acquire necessary locks and fix any deleted JOIN RTE entries. */ - AcquireRewriteLocks(rule_action); + AcquireRewriteLocks(rule_action, false); (void) acquireLocksOnSubLinks(rule_qual, NULL); current_varno = rt_index; @@ -308,7 +328,7 @@ rewriteRuleAction(Query *parsetree, OffsetVarNodes((Node *) sub_action, rt_length, 0); OffsetVarNodes(rule_qual, rt_length, 0); - /* but references to *OLD* should point at original rt_index */ + /* but references to OLD should point at original rt_index */ ChangeVarNodes((Node *) sub_action, PRS2_OLD_VARNO + rt_length, rt_index, 0); ChangeVarNodes(rule_qual, @@ -345,6 +365,37 @@ rewriteRuleAction(Query *parsetree, sub_action->rtable = list_concat((List *) copyObject(parsetree->rtable), sub_action->rtable); + /* + * There could have been some SubLinks in parsetree's rtable, in which + * case we'd better mark the sub_action correctly. + */ + if (parsetree->hasSubLinks && !sub_action->hasSubLinks) + { + ListCell *lc; + + foreach(lc, parsetree->rtable) + { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); + + switch (rte->rtekind) + { + case RTE_FUNCTION: + sub_action->hasSubLinks = + checkExprHasSubLink(rte->funcexpr); + break; + case RTE_VALUES: + sub_action->hasSubLinks = + checkExprHasSubLink((Node *) rte->values_lists); + break; + default: + /* other RTE types don't contain bare expressions */ + break; + } + if (sub_action->hasSubLinks) + break; /* no need to keep scanning rtable */ + } + } + /* * Each rule action's jointree should be the main parsetree's jointree * plus that rule's jointree, but usually *without* the original rtindex @@ -423,7 +474,8 @@ rewriteRuleAction(Query *parsetree, sub_action->rtable), parsetree->targetList, event, - current_varno); + current_varno, + NULL); if (sub_action_ptr) *sub_action_ptr = sub_action; else @@ -453,7 +505,16 @@ rewriteRuleAction(Query *parsetree, parsetree->rtable), rule_action->returningList, CMD_SELECT, - 0); + 0, + &rule_action->hasSubLinks); + + /* + * There could have been some SubLinks in parsetree's returningList, + * in which case we'd better mark the rule_action correctly. + */ + if (parsetree->hasSubLinks && !rule_action->hasSubLinks) + rule_action->hasSubLinks = + checkExprHasSubLink((Node *) rule_action->returningList); } return rule_action; @@ -496,7 +557,7 @@ adjustJoinTreeList(Query *parsetree, bool removert, int rt_index) /* - * rewriteTargetList - rewrite INSERT/UPDATE targetlist into standard form + * rewriteTargetListIU - rewrite INSERT/UPDATE targetlist into standard form * * This has the following responsibilities: * @@ -508,7 +569,14 @@ adjustJoinTreeList(Query *parsetree, bool removert, int rt_index) * and UPDATE, replace explicit DEFAULT specifications with column default * expressions. * - * 2. Merge multiple entries for the same target attribute, or declare error + * 2. For an UPDATE on a view, add tlist entries for any unassigned-to + * attributes, assigning them their old values. These will later get + * expanded to the output values of the view. (This is equivalent to what + * the planner's expand_targetlist() will do for UPDATE on a regular table, + * but it's more convenient to do it here while we still have easy access + * to the view's original RT index.) + * + * 3. Merge multiple entries for the same target attribute, or declare error * if we can't. Multiple entries are only allowed for INSERT/UPDATE of * portions of an array or record field, for example * UPDATE table SET foo[2] = 42, foo[4] = 43; @@ -516,13 +584,13 @@ adjustJoinTreeList(Query *parsetree, bool removert, int rt_index) * the expression we want to produce in this case is like * foo = array_set(array_set(foo, 2, 42), 4, 43) * - * 3. Sort the tlist into standard order: non-junk fields in order by resno, + * 4. Sort the tlist into standard order: non-junk fields in order by resno, * then junk fields (these in no particular order). * - * We must do items 1 and 2 before firing rewrite rules, else rewritten - * references to NEW.foo will produce wrong or incomplete results. Item 3 + * We must do items 1,2,3 before firing rewrite rules, else rewritten + * references to NEW.foo will produce wrong or incomplete results. Item 4 * is not needed for rewriting, but will be needed by the planner, and we - * can do it essentially for free while handling items 1 and 2. + * can do it essentially for free while handling the other items. * * If attrno_list isn't NULL, we return an additional output besides the * rewritten targetlist: an integer list of the assigned-to attnums, in @@ -530,8 +598,8 @@ adjustJoinTreeList(Query *parsetree, bool removert, int rt_index) * processing VALUES RTEs. */ static void -rewriteTargetList(Query *parsetree, Relation target_relation, - List **attrno_list) +rewriteTargetListIU(Query *parsetree, Relation target_relation, + List **attrno_list) { CmdType commandType = parsetree->commandType; TargetEntry **new_tles; @@ -653,6 +721,7 @@ rewriteTargetList(Query *parsetree, Relation target_relation, InvalidOid, -1, att_tup->atttypid, COERCE_IMPLICIT_CAST, + -1, false, false); } @@ -665,6 +734,28 @@ rewriteTargetList(Query *parsetree, Relation target_relation, false); } + /* + * For an UPDATE on a view, provide a dummy entry whenever there is + * no explicit assignment. + */ + if (new_tle == NULL && commandType == CMD_UPDATE && + target_relation->rd_rel->relkind == RELKIND_VIEW) + { + Node *new_expr; + + new_expr = (Node *) makeVar(parsetree->resultRelation, + attrno, + att_tup->atttypid, + att_tup->atttypmod, + att_tup->attcollation, + 0); + + new_tle = makeTargetEntry((Expr *) new_expr, + attrno, + pstrdup(NameStr(att_tup->attname)), + false); + } + if (new_tle) new_tlist = lappend(new_tlist, new_tle); } @@ -885,7 +976,8 @@ build_column_default(Relation rel, int attrno) expr, exprtype, atttype, atttypmod, COERCION_ASSIGNMENT, - COERCE_IMPLICIT_CAST); + COERCE_IMPLICIT_CAST, + -1); if (expr == NULL) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), @@ -925,8 +1017,8 @@ searchForDefault(RangeTblEntry *rte) /* * When processing INSERT ... VALUES with a VALUES RTE (ie, multiple VALUES * lists), we have to replace any DEFAULT items in the VALUES lists with - * the appropriate default expressions. The other aspects of rewriteTargetList - * need be applied only to the query's targetlist proper. + * the appropriate default expressions. The other aspects of targetlist + * rewriting need be applied only to the query's targetlist proper. * * Note that we currently can't support subscripted or field assignment * in the multi-VALUES case. The targetlist will contain simple Vars @@ -992,6 +1084,7 @@ rewriteValuesRTE(RangeTblEntry *rte, Relation target_relation, List *attrnos) InvalidOid, -1, att_tup->atttypid, COERCE_IMPLICIT_CAST, + -1, false, false); } @@ -1006,6 +1099,62 @@ rewriteValuesRTE(RangeTblEntry *rte, Relation target_relation, List *attrnos) } +/* + * rewriteTargetListUD - rewrite UPDATE/DELETE targetlist as needed + * + * This function adds a "junk" TLE that is needed to allow the executor to + * find the original row for the update or delete. When the target relation + * is a regular table, the junk TLE emits the ctid attribute of the original + * row. When the target relation is a view, there is no ctid, so we instead + * emit a whole-row Var that will contain the "old" values of the view row. + * + * For UPDATE queries, this is applied after rewriteTargetListIU. The + * ordering isn't actually critical at the moment. + */ +static void +rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte, + Relation target_relation) +{ + Var *var; + const char *attrname; + TargetEntry *tle; + + if (target_relation->rd_rel->relkind == RELKIND_RELATION) + { + /* + * Emit CTID so that executor can find the row to update or delete. + */ + var = makeVar(parsetree->resultRelation, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + + attrname = "ctid"; + } + else + { + /* + * Emit whole-row Var so that executor will have the "old" view row + * to pass to the INSTEAD OF trigger. + */ + var = makeWholeRowVar(target_rte, + parsetree->resultRelation, + 0); + + attrname = "wholerow"; + } + + tle = makeTargetEntry((Expr *) var, + list_length(parsetree->targetList) + 1, + pstrdup(attrname), + true); + + parsetree->targetList = lappend(parsetree->targetList, tle); +} + + /* * matchLocks - * match the list of locks and returns the matching rules @@ -1035,6 +1184,28 @@ matchLocks(CmdType event, { RewriteRule *oneLock = rulelocks->rules[i]; + /* + * Suppress ON INSERT/UPDATE/DELETE rules that are disabled or + * configured to not fire during the current sessions replication + * role. ON SELECT rules will always be applied in order to keep views + * working even in LOCAL or REPLICA role. + */ + if (oneLock->event != CMD_SELECT) + { + if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA) + { + if (oneLock->enabled == RULE_FIRES_ON_ORIGIN || + oneLock->enabled == RULE_DISABLED) + continue; + } + else /* ORIGIN or LOCAL ROLE */ + { + if (oneLock->enabled == RULE_FIRES_ON_REPLICA || + oneLock->enabled == RULE_DISABLED) + continue; + } + } + if (oneLock->event == event) { if (parsetree->commandType != CMD_SELECT || @@ -1059,7 +1230,8 @@ ApplyRetrieveRule(Query *parsetree, int rt_index, bool relation_level, Relation relation, - List *activeRIRs) + List *activeRIRs, + bool forUpdatePushedDown) { Query *rule_action; RangeTblEntry *rte, @@ -1073,22 +1245,90 @@ ApplyRetrieveRule(Query *parsetree, if (!relation_level) elog(ERROR, "cannot handle per-attribute ON SELECT rule"); + if (rt_index == parsetree->resultRelation) + { + /* + * We have a view as the result relation of the query, and it wasn't + * rewritten by any rule. This case is supported if there is an + * INSTEAD OF trigger that will trap attempts to insert/update/delete + * view rows. The executor will check that; for the moment just plow + * ahead. We have two cases: + * + * For INSERT, we needn't do anything. The unmodified RTE will serve + * fine as the result relation. + * + * For UPDATE/DELETE, we need to expand the view so as to have source + * data for the operation. But we also need an unmodified RTE to + * serve as the target. So, copy the RTE and add the copy to the + * rangetable. Note that the copy does not get added to the jointree. + * Also note that there's a hack in fireRIRrules to avoid calling + * this function again when it arrives at the copied RTE. + */ + if (parsetree->commandType == CMD_INSERT) + return parsetree; + else if (parsetree->commandType == CMD_UPDATE || + parsetree->commandType == CMD_DELETE) + { + RangeTblEntry *newrte; + + rte = rt_fetch(rt_index, parsetree->rtable); + newrte = copyObject(rte); + parsetree->rtable = lappend(parsetree->rtable, newrte); + parsetree->resultRelation = list_length(parsetree->rtable); + + /* + * There's no need to do permissions checks twice, so wipe out + * the permissions info for the original RTE (we prefer to keep + * the bits set on the result RTE). + */ + rte->requiredPerms = 0; + rte->checkAsUser = InvalidOid; + rte->selectedCols = NULL; + rte->modifiedCols = NULL; + + /* + * For the most part, Vars referencing the view should remain as + * they are, meaning that they implicitly represent OLD values. + * But in the RETURNING list if any, we want such Vars to + * represent NEW values, so change them to reference the new RTE. + * + * Since ChangeVarNodes scribbles on the tree in-place, copy the + * RETURNING list first for safety. + */ + parsetree->returningList = copyObject(parsetree->returningList); + ChangeVarNodes((Node *) parsetree->returningList, rt_index, + parsetree->resultRelation, 0); + + /* Now, continue with expanding the original view RTE */ + } + else + elog(ERROR, "unrecognized commandType: %d", + (int) parsetree->commandType); + } + + /* + * If FOR UPDATE/SHARE of view, be sure we get right initial lock on the + * relations it references. + */ + rc = get_parse_rowmark(parsetree, rt_index); + forUpdatePushedDown |= (rc != NULL); + /* * Make a modifiable copy of the view query, and acquire needed locks on * the relations it mentions. */ rule_action = copyObject(linitial(rule->actions)); - AcquireRewriteLocks(rule_action); + AcquireRewriteLocks(rule_action, forUpdatePushedDown); /* * Recursively expand any view references inside the view. */ - rule_action = fireRIRrules(rule_action, activeRIRs); + rule_action = fireRIRrules(rule_action, activeRIRs, forUpdatePushedDown); /* - * VIEWs are really easy --- just plug the view query in as a subselect, - * replacing the relation's original RTE. + * Now, plug the view query in as a subselect, replacing the relation's + * original RTE. */ rte = rt_fetch(rt_index, parsetree->rtable); @@ -1099,34 +1339,31 @@ ApplyRetrieveRule(Query *parsetree, /* * We move the view's permission check data down to its rangetable. The - * checks will actually be done against the *OLD* entry therein. + * checks will actually be done against the OLD entry therein. */ subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable); Assert(subrte->relid == relation->rd_id); subrte->requiredPerms = rte->requiredPerms; subrte->checkAsUser = rte->checkAsUser; + subrte->selectedCols = rte->selectedCols; + subrte->modifiedCols = rte->modifiedCols; rte->requiredPerms = 0; /* no permission check on subquery itself */ rte->checkAsUser = InvalidOid; + rte->selectedCols = NULL; + rte->modifiedCols = NULL; /* - * FOR UPDATE/SHARE of view? + * If FOR UPDATE/SHARE of view, mark all the contained tables as implicit + * FOR UPDATE/SHARE, the same as the parser would have done if the view's + * subquery had been written out explicitly. + * + * Note: we don't consider forUpdatePushedDown here; such marks will be + * made by recursing from the upper level in markQueryForLocking. */ - if ((rc = get_rowmark(parsetree, rt_index)) != NULL) - { - /* - * Remove the view from the list of rels that will actually be marked - * FOR UPDATE/SHARE by the executor. It will still be access-checked - * for write access, though. - */ - parsetree->rowMarks = list_delete_ptr(parsetree->rowMarks, rc); - - /* - * Set up the view's referenced tables as if FOR UPDATE/SHARE. - */ + if (rc != NULL) markQueryForLocking(rule_action, (Node *) rule_action->jointree, - rc->forUpdate, rc->noWait); - } + rc->forUpdate, rc->noWait, true); return parsetree; } @@ -1143,7 +1380,8 @@ ApplyRetrieveRule(Query *parsetree, * to scan the jointree to determine which rels are used. */ static void -markQueryForLocking(Query *qry, Node *jtnode, bool forUpdate, bool noWait) +markQueryForLocking(Query *qry, Node *jtnode, + bool forUpdate, bool noWait, bool pushedDown) { if (jtnode == NULL) return; @@ -1154,15 +1392,21 @@ markQueryForLocking(Query *qry, Node *jtnode, bool forUpdate, bool noWait) if (rte->rtekind == RTE_RELATION) { - applyLockingClause(qry, rti, forUpdate, noWait); - rte->requiredPerms |= ACL_SELECT_FOR_UPDATE; + /* ignore foreign tables */ + if (get_rel_relkind(rte->relid) != RELKIND_FOREIGN_TABLE) + { + applyLockingClause(qry, rti, forUpdate, noWait, pushedDown); + rte->requiredPerms |= ACL_SELECT_FOR_UPDATE; + } } else if (rte->rtekind == RTE_SUBQUERY) { + applyLockingClause(qry, rti, forUpdate, noWait, pushedDown); /* FOR UPDATE/SHARE of subquery is propagated to subquery's rels */ markQueryForLocking(rte->subquery, (Node *) rte->subquery->jointree, - forUpdate, noWait); + forUpdate, noWait, true); } + /* other RTE types are unaffected by FOR UPDATE */ } else if (IsA(jtnode, FromExpr)) { @@ -1170,14 +1414,14 @@ markQueryForLocking(Query *qry, Node *jtnode, bool forUpdate, bool noWait) ListCell *l; foreach(l, f->fromlist) - markQueryForLocking(qry, lfirst(l), forUpdate, noWait); + markQueryForLocking(qry, lfirst(l), forUpdate, noWait, pushedDown); } else if (IsA(jtnode, JoinExpr)) { JoinExpr *j = (JoinExpr *) jtnode; - markQueryForLocking(qry, j->larg, forUpdate, noWait); - markQueryForLocking(qry, j->rarg, forUpdate, noWait); + markQueryForLocking(qry, j->larg, forUpdate, noWait, pushedDown); + markQueryForLocking(qry, j->rarg, forUpdate, noWait, pushedDown); } else elog(ERROR, "unrecognized node type: %d", @@ -1209,7 +1453,7 @@ fireRIRonSubLink(Node *node, List *activeRIRs) /* Do what we came for */ sub->subselect = (Node *) fireRIRrules((Query *) sub->subselect, - activeRIRs); + activeRIRs, false); /* Fall through to process lefthand args of SubLink */ } @@ -1227,9 +1471,11 @@ fireRIRonSubLink(Node *node, List *activeRIRs) * Apply all RIR rules on each rangetable entry in a query */ static Query * -fireRIRrules(Query *parsetree, List *activeRIRs) +fireRIRrules(Query *parsetree, List *activeRIRs, bool forUpdatePushedDown) { + int origResultRelation = parsetree->resultRelation; int rt_index; + ListCell *lc; /* * don't try to convert this into a foreach loop, because rtable list can @@ -1256,7 +1502,9 @@ fireRIRrules(Query *parsetree, List *activeRIRs) */ if (rte->rtekind == RTE_SUBQUERY) { - rte->subquery = fireRIRrules(rte->subquery, activeRIRs); + rte->subquery = fireRIRrules(rte->subquery, activeRIRs, + (forUpdatePushedDown || + get_parse_rowmark(parsetree, rt_index) != NULL)); continue; } @@ -1277,6 +1525,14 @@ fireRIRrules(Query *parsetree, List *activeRIRs) !rangeTableEntry_used((Node *) parsetree, rt_index, 0)) continue; + /* + * Also, if this is a new result relation introduced by + * ApplyRetrieveRule, we don't want to do anything more with it. + */ + if (rt_index == parsetree->resultRelation && + rt_index != origResultRelation) + continue; + /* * We can use NoLock here since either the parser or * AcquireRewriteLocks should have locked the rel already. @@ -1333,7 +1589,8 @@ fireRIRrules(Query *parsetree, List *activeRIRs) rt_index, rule->attrno == -1, rel, - activeRIRs); + activeRIRs, + forUpdatePushedDown); } activeRIRs = list_delete_first(activeRIRs); @@ -1342,13 +1599,22 @@ fireRIRrules(Query *parsetree, List *activeRIRs) heap_close(rel, NoLock); } + /* Recurse into subqueries in WITH */ + foreach(lc, parsetree->cteList) + { + CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc); + + cte->ctequery = (Node *) + fireRIRrules((Query *) cte->ctequery, activeRIRs, false); + } + /* * Recurse into sublink subqueries, too. But we already did the ones in - * the rtable. + * the rtable and cteList. */ if (parsetree->hasSubLinks) query_tree_walker(parsetree, fireRIRonSubLink, (void *) activeRIRs, - QTW_IGNORE_RT_SUBQUERIES); + QTW_IGNORE_RC_SUBQUERIES); return parsetree; } @@ -1394,7 +1660,8 @@ CopyAndAddInvertedQual(Query *parsetree, rt_fetch(rt_index, parsetree->rtable), parsetree->targetList, event, - rt_index); + rt_index, + &parsetree->hasSubLinks); /* And attach the fixed qual */ AddInvertedQual(parsetree, new_qual); @@ -1529,7 +1796,8 @@ RewriteQuery(Query *parsetree, List *rewrite_events) List *rewritten = NIL; /* - * If the statement is an update, insert or delete - fire rules on it. + * If the statement is an insert, update, or delete, adjust its targetlist + * as needed, and then fire INSERT/UPDATE/DELETE rules on it. * * SELECT rules are handled later when we have all the queries that should * get executed. Also, utilities aren't rewritten at all (do we still @@ -1554,13 +1822,9 @@ RewriteQuery(Query *parsetree, List *rewrite_events) rt_entry_relation = heap_open(rt_entry->relid, NoLock); /* - * If it's an INSERT or UPDATE, rewrite the targetlist into standard - * form. This will be needed by the planner anyway, and doing it now - * ensures that any references to NEW.field will behave sanely. + * Rewrite the targetlist as needed for the command type. */ - if (event == CMD_UPDATE) - rewriteTargetList(parsetree, rt_entry_relation, NULL); - else if (event == CMD_INSERT) + if (event == CMD_INSERT) { RangeTblEntry *values_rte = NULL; @@ -1587,16 +1851,27 @@ RewriteQuery(Query *parsetree, List *rewrite_events) List *attrnos; /* Process the main targetlist ... */ - rewriteTargetList(parsetree, rt_entry_relation, &attrnos); + rewriteTargetListIU(parsetree, rt_entry_relation, &attrnos); /* ... and the VALUES expression lists */ rewriteValuesRTE(values_rte, rt_entry_relation, attrnos); } else { /* Process just the main targetlist */ - rewriteTargetList(parsetree, rt_entry_relation, NULL); + rewriteTargetListIU(parsetree, rt_entry_relation, NULL); } } + else if (event == CMD_UPDATE) + { + rewriteTargetListIU(parsetree, rt_entry_relation, NULL); + rewriteTargetListUD(parsetree, rt_entry, rt_entry_relation); + } + else if (event == CMD_DELETE) + { + rewriteTargetListUD(parsetree, rt_entry, rt_entry_relation); + } + else + elog(ERROR, "unrecognized commandType: %d", (int) event); /* * Collect and apply the appropriate rules. @@ -1670,22 +1945,22 @@ RewriteQuery(Query *parsetree, List *rewrite_events) case CMD_INSERT: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot perform INSERT RETURNING on relation \"%s\"", - RelationGetRelationName(rt_entry_relation)), + errmsg("cannot perform INSERT RETURNING on relation \"%s\"", + RelationGetRelationName(rt_entry_relation)), errhint("You need an unconditional ON INSERT DO INSTEAD rule with a RETURNING clause."))); break; case CMD_UPDATE: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot perform UPDATE RETURNING on relation \"%s\"", - RelationGetRelationName(rt_entry_relation)), + errmsg("cannot perform UPDATE RETURNING on relation \"%s\"", + RelationGetRelationName(rt_entry_relation)), errhint("You need an unconditional ON UPDATE DO INSTEAD rule with a RETURNING clause."))); break; case CMD_DELETE: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot perform DELETE RETURNING on relation \"%s\"", - RelationGetRelationName(rt_entry_relation)), + errmsg("cannot perform DELETE RETURNING on relation \"%s\"", + RelationGetRelationName(rt_entry_relation)), errhint("You need an unconditional ON DELETE DO INSTEAD rule with a RETURNING clause."))); break; default: @@ -1745,7 +2020,7 @@ List * QueryRewrite(Query *parsetree) { List *querylist; - List *results = NIL; + List *results; ListCell *l; CmdType origCmdType; bool foundOriginalQuery; @@ -1763,50 +2038,12 @@ QueryRewrite(Query *parsetree) * * Apply all the RIR rules on each query */ + results = NIL; foreach(l, querylist) { Query *query = (Query *) lfirst(l); - query = fireRIRrules(query, NIL); - - /* - * If the query target was rewritten as a view, complain. - */ - if (query->resultRelation) - { - RangeTblEntry *rte = rt_fetch(query->resultRelation, - query->rtable); - - if (rte->rtekind == RTE_SUBQUERY) - { - switch (query->commandType) - { - case CMD_INSERT: - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot insert into a view"), - errhint("You need an unconditional ON INSERT DO INSTEAD rule."))); - break; - case CMD_UPDATE: - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot update a view"), - errhint("You need an unconditional ON UPDATE DO INSTEAD rule."))); - break; - case CMD_DELETE: - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot delete from a view"), - errhint("You need an unconditional ON DELETE DO INSTEAD rule."))); - break; - default: - elog(ERROR, "unrecognized commandType: %d", - (int) query->commandType); - break; - } - } - } - + query = fireRIRrules(query, NIL, false); results = lappend(results, query); }