X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fbackend%2Frewrite%2FrewriteHandler.c;h=3a50642fce8008d6b99b816515022cd10773d232;hb=bb742407947ad1cbf19355d24282380d576e7654;hp=d83e8ac45805bec8661681b51c9cefc552a45b20;hpb=375369acd1c621bdc683c58bc9c31d4e79d14849;p=postgresql diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index d83e8ac458..3a50642fce 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -3,34 +3,30 @@ * rewriteHandler.c * Primary module of query rewriter. * - * Portions Copyright (c) 1996-2003, 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.134 2004/04/01 21:28:44 tgl Exp $ + * src/backend/rewrite/rewriteHandler.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/heapam.h" -#include "catalog/pg_operator.h" +#include "access/sysattr.h" #include "catalog/pg_type.h" -#include "miscadmin.h" #include "nodes/makefuncs.h" -#include "optimizer/clauses.h" -#include "optimizer/prep.h" -#include "optimizer/var.h" +#include "nodes/nodeFuncs.h" #include "parser/analyze.h" #include "parser/parse_coerce.h" -#include "parser/parse_expr.h" -#include "parser/parse_oper.h" -#include "parser/parse_type.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 */ @@ -40,33 +36,262 @@ typedef struct rewrite_event CmdType event; /* type of rule being fired */ } rewrite_event; +static bool acquireLocksOnSubLinks(Node *node, void *context); static Query *rewriteRuleAction(Query *parsetree, Query *rule_action, Node *rule_qual, int rt_index, - CmdType event); + CmdType event, + bool *returning_flag); static List *adjustJoinTreeList(Query *parsetree, bool removert, int rt_index); -static void rewriteTargetList(Query *parsetree, Relation target_relation); +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 void markQueryForUpdate(Query *qry, bool skipOldNew); + 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 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); + + +/* + * AcquireRewriteLocks - + * Acquire suitable locks on all the relations mentioned in the Query. + * 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 + * first done a copyObject() to make a writable copy of the querytree in the + * current memory context. + * + * This processing can, and for efficiency's sake should, be skipped when the + * querytree has just been built by the parser: parse analysis already got + * all the same locks we'd get here, and the parser will have omitted dropped + * columns from JOINs to begin with. But we must do this whenever we are + * dealing with a querytree produced earlier than the current command. + * + * About JOINs and dropped columns: although the parser never includes an + * already-dropped column in a JOIN RTE's alias var list, it is possible for + * such a list in a stored rule to include references to dropped columns. + * (If the column is not explicitly referenced anywhere else in the query, + * the dependency mechanism won't consider it used by the rule and so won't + * prevent the column drop.) To support get_rte_attribute_is_dropped(), + * we replace join alias vars that reference dropped columns with NULL Const + * nodes. + * + * (In PostgreSQL 8.0, we did not do this processing but instead had + * get_rte_attribute_is_dropped() recurse to detect dropped columns in joins. + * That approach had horrible performance unfortunately; in particular + * construction of a nested join was O(N^2) in the nesting depth.) + */ +void +AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown) +{ + ListCell *l; + int rt_index; + + /* + * First, process RTEs of the current query level. + */ + rt_index = 0; + foreach(l, parsetree->rtable) + { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(l); + Relation rel; + LOCKMODE lockmode; + List *newaliasvars; + Index curinputvarno; + RangeTblEntry *curinputrte; + ListCell *ll; + + ++rt_index; + switch (rte->rtekind) + { + case RTE_RELATION: + + /* + * Grab the appropriate lock type for the relation, and do not + * release it until end of transaction. This protects the + * rewriter and planner against schema changes mid-query. + * + * If the relation is the query's result relation, then we + * need RowExclusiveLock. Otherwise, check to see if the + * relation is accessed FOR UPDATE/SHARE or not. We can't + * just grab AccessShareLock because then the executor would + * be trying to upgrade the lock, leading to possible + * deadlocks. + */ + if (rt_index == parsetree->resultRelation) + lockmode = RowExclusiveLock; + else if (forUpdatePushedDown || + get_parse_rowmark(parsetree, rt_index) != NULL) + lockmode = RowShareLock; + else + lockmode = AccessShareLock; + + rel = heap_open(rte->relid, lockmode); + heap_close(rel, NoLock); + break; + + case RTE_JOIN: + + /* + * Scan the join's alias var list to see if any columns have + * been dropped, and if so replace those Vars with NULL + * Consts. + * + * Since a join has only two inputs, we can expect to see + * multiple references to the same input RTE; optimize away + * multiple fetches. + */ + newaliasvars = NIL; + curinputvarno = 0; + curinputrte = NULL; + foreach(ll, rte->joinaliasvars) + { + Var *aliasvar = (Var *) lfirst(ll); + + /* + * If the list item isn't a simple Var, then it must + * represent a merged column, ie a USING column, and so it + * couldn't possibly be dropped, since it's referenced in + * the join clause. (Conceivably it could also be a NULL + * constant already? But that's OK too.) + */ + if (IsA(aliasvar, Var)) + { + /* + * The elements of an alias list have to refer to + * earlier RTEs of the same rtable, because that's the + * order the planner builds things in. So we already + * processed the referenced RTE, and so it's safe to + * use get_rte_attribute_is_dropped on it. (This might + * not hold after rewriting or planning, but it's OK + * to assume here.) + */ + Assert(aliasvar->varlevelsup == 0); + if (aliasvar->varno != curinputvarno) + { + curinputvarno = aliasvar->varno; + if (curinputvarno >= rt_index) + elog(ERROR, "unexpected varno %d in JOIN RTE %d", + curinputvarno, rt_index); + curinputrte = rt_fetch(curinputvarno, + parsetree->rtable); + } + if (get_rte_attribute_is_dropped(curinputrte, + aliasvar->varattno)) + { + /* + * can't use vartype here, since that might be a + * now-dropped type OID, but it doesn't really + * matter what type the Const claims to be. + */ + aliasvar = (Var *) makeNullConst(INT4OID, -1); + } + } + newaliasvars = lappend(newaliasvars, aliasvar); + } + rte->joinaliasvars = newaliasvars; + break; + + case RTE_SUBQUERY: + + /* + * The subquery RTE itself is all right, but we have to + * recurse to process the represented subquery. + */ + AcquireRewriteLocks(rte->subquery, + (forUpdatePushedDown || + get_parse_rowmark(parsetree, rt_index) != NULL)); + break; + + default: + /* ignore other types of RTEs */ + break; + } + } + + /* 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 and cteList. + */ + if (parsetree->hasSubLinks) + query_tree_walker(parsetree, acquireLocksOnSubLinks, NULL, + QTW_IGNORE_RC_SUBQUERIES); +} + +/* + * Walker to find sublink subqueries for AcquireRewriteLocks + */ +static bool +acquireLocksOnSubLinks(Node *node, void *context) +{ + if (node == NULL) + return false; + if (IsA(node, SubLink)) + { + SubLink *sub = (SubLink *) node; + + /* Do what we came for */ + AcquireRewriteLocks((Query *) sub->subselect, false); + /* Fall through to process lefthand args of SubLink */ + } + + /* + * Do NOT recurse into Query nodes, because AcquireRewriteLocks already + * processed subselects of subselects for us. + */ + return expression_tree_walker(node, acquireLocksOnSubLinks, context); +} /* * rewriteRuleAction - * Rewrite the rule action with appropriate qualifiers (taken from * the triggering query). + * + * Input arguments: + * parsetree - original query + * rule_action - one action (query) of a rule + * rule_qual - WHERE condition of rule, or NULL if unconditional + * rt_index - RT index of result relation in original query + * event - type of rule event + * Output arguments: + * *returning_flag - set TRUE if we rewrite RETURNING clause in rule_action + * (must be initialized to FALSE) + * Return value: + * rewritten form of rule_action */ static Query * rewriteRuleAction(Query *parsetree, Query *rule_action, Node *rule_qual, int rt_index, - CmdType event) + CmdType event, + bool *returning_flag) { int current_varno, new_varno; @@ -75,29 +300,35 @@ rewriteRuleAction(Query *parsetree, Query **sub_action_ptr; /* - * Make modifiable copies of rule action and qual (what we're passed - * are the stored versions in the relcache; don't touch 'em!). + * Make modifiable copies of rule action and qual (what we're passed are + * the stored versions in the relcache; don't touch 'em!). */ rule_action = (Query *) copyObject(rule_action); rule_qual = (Node *) copyObject(rule_qual); + /* + * Acquire necessary locks and fix any deleted JOIN RTE entries. + */ + AcquireRewriteLocks(rule_action, false); + (void) acquireLocksOnSubLinks(rule_qual, NULL); + current_varno = rt_index; - rt_length = length(parsetree->rtable); + rt_length = list_length(parsetree->rtable); new_varno = PRS2_NEW_VARNO + rt_length; /* - * Adjust rule action and qual to offset its varnos, so that we can - * merge its rtable with the main parsetree's rtable. + * Adjust rule action and qual to offset its varnos, so that we can merge + * its rtable with 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!). + * will be in the SELECT part, and we have to modify that rather than the + * top-level INSERT (kluge!). */ sub_action = getInsertSelectQuery(rule_action, &sub_action_ptr); 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, @@ -106,45 +337,75 @@ rewriteRuleAction(Query *parsetree, /* * Generate expanded rtable consisting of main parsetree's rtable plus * rule action's rtable; this becomes the complete rtable for the rule - * action. Some of the entries may be unused after we finish - * rewriting, but we leave them all in place for two reasons: + * action. Some of the entries may be unused after we finish rewriting, + * but we leave them all in place for two reasons: * - * * We'd have a much harder job to adjust the query's varnos - * if we selectively removed RT entries. + * We'd have a much harder job to adjust the query's varnos if we + * selectively removed RT entries. * - * * If the rule is INSTEAD, then the original query won't be - * executed at all, and so its rtable must be preserved so that - * the executor will do the correct permissions checks on it. + * If the rule is INSTEAD, then the original query won't be executed at + * all, and so its rtable must be preserved so that the executor will do + * the correct permissions checks on it. * * RT entries that are not referenced in the completed jointree will be - * ignored by the planner, so they do not affect query semantics. But - * any permissions checks specified in them will be applied during - * executor startup (see ExecCheckRTEPerms()). This allows us to check - * that the caller has, say, insert-permission on a view, when the view - * is not semantically referenced at all in the resulting query. + * ignored by the planner, so they do not affect query semantics. But any + * permissions checks specified in them will be applied during executor + * startup (see ExecCheckRTEPerms()). This allows us to check that the + * caller has, say, insert-permission on a view, when the view is not + * semantically referenced at all in the resulting query. * * When a rule is not INSTEAD, the permissions checks done on its copied * RT entries will be redundant with those done during execution of the * original query, but we don't bother to treat that case differently. * * NOTE: because planner will destructively alter rtable, we must ensure - * that rule action's rtable is separate and shares no substructure - * with the main rtable. Hence do a deep copy here. + * that rule action's rtable is separate and shares no substructure with + * the main rtable. Hence do a deep copy here. + */ + 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. */ - sub_action->rtable = nconc((List *) copyObject(parsetree->rtable), - sub_action->rtable); + 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 that we're replacing (if present, which it won't be for - * INSERT). Note that if the rule action refers to OLD, its jointree - * will add a reference to rt_index. If the rule action doesn't refer - * to OLD, but either the rule_qual or the user query quals do, then - * we need to keep the original rtindex in the jointree to provide - * data for the quals. We don't want the original rtindex to be - * joined twice, however, so avoid keeping it if the rule action - * mentions it. + * plus that rule's jointree, but usually *without* the original rtindex + * that we're replacing (if present, which it won't be for INSERT). Note + * that if the rule action refers to OLD, its jointree will add a + * reference to rt_index. If the rule action doesn't refer to OLD, but + * either the rule_qual or the user query quals do, then we need to keep + * the original rtindex in the jointree to provide data for the quals. We + * don't want the original rtindex to be joined twice, however, so avoid + * keeping it if the rule action mentions it. * * As above, the action's jointree must not share substructure with the * main parsetree's. @@ -158,15 +419,15 @@ rewriteRuleAction(Query *parsetree, keeporig = (!rangeTableEntry_used((Node *) sub_action->jointree, rt_index, 0)) && (rangeTableEntry_used(rule_qual, rt_index, 0) || - rangeTableEntry_used(parsetree->jointree->quals, rt_index, 0)); + rangeTableEntry_used(parsetree->jointree->quals, rt_index, 0)); newjointree = adjustJoinTreeList(parsetree, !keeporig, rt_index); if (newjointree != NIL) { /* - * If sub_action is a setop, manipulating its jointree will do - * no good at all, because the jointree is dummy. (Perhaps - * someday we could push the joining and quals down to the - * member statements of the setop?) + * If sub_action is a setop, manipulating its jointree will do no + * good at all, because the jointree is dummy. (Perhaps someday + * we could push the joining and quals down to the member + * statements of the setop?) */ if (sub_action->setOperations != NULL) ereport(ERROR, @@ -174,26 +435,22 @@ rewriteRuleAction(Query *parsetree, errmsg("conditional UNION/INTERSECT/EXCEPT statements are not implemented"))); sub_action->jointree->fromlist = - nconc(newjointree, sub_action->jointree->fromlist); + list_concat(newjointree, sub_action->jointree->fromlist); + + /* + * There could have been some SubLinks in newjointree, in which + * case we'd better mark the sub_action correctly. + */ + if (parsetree->hasSubLinks && !sub_action->hasSubLinks) + sub_action->hasSubLinks = + checkExprHasSubLink((Node *) newjointree); } } /* - * 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. (XXX this should probably be - * handled by AddQual and friends, not here...) - */ - 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 + * 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, rule_qual); @@ -207,20 +464,59 @@ rewriteRuleAction(Query *parsetree, * apply it to sub_action; we have to remember to update the sublink * inside rule_action, too. */ - if (event == CMD_INSERT || event == CMD_UPDATE) + if ((event == CMD_INSERT || event == CMD_UPDATE) && + sub_action->commandType != CMD_UTILITY) { sub_action = (Query *) ResolveNew((Node *) sub_action, new_varno, 0, + rt_fetch(new_varno, + sub_action->rtable), parsetree->targetList, event, - current_varno); + current_varno, + NULL); if (sub_action_ptr) *sub_action_ptr = sub_action; else rule_action = sub_action; } + /* + * If rule_action has a RETURNING clause, then either throw it away if the + * triggering query has no RETURNING clause, or rewrite it to emit what + * the triggering query's RETURNING clause asks for. Throw an error if + * more than one rule has a RETURNING clause. + */ + if (!parsetree->returningList) + rule_action->returningList = NIL; + else if (rule_action->returningList) + { + if (*returning_flag) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot have RETURNING lists in multiple rules"))); + *returning_flag = true; + rule_action->returningList = (List *) + ResolveNew((Node *) parsetree->returningList, + parsetree->resultRelation, + 0, + rt_fetch(parsetree->resultRelation, + parsetree->rtable), + rule_action->returningList, + CMD_SELECT, + 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; } @@ -236,19 +532,22 @@ static List * adjustJoinTreeList(Query *parsetree, bool removert, int rt_index) { List *newjointree = copyObject(parsetree->jointree->fromlist); - List *jjt; + ListCell *l; if (removert) { - foreach(jjt, newjointree) + foreach(l, newjointree) { - RangeTblRef *rtr = lfirst(jjt); + RangeTblRef *rtr = lfirst(l); if (IsA(rtr, RangeTblRef) && rtr->rtindex == rt_index) { - newjointree = lremove(rtr, newjointree); - /* foreach is safe because we exit loop after lremove... */ + newjointree = list_delete_ptr(newjointree, rtr); + + /* + * foreach is safe because we exit loop after list_delete... + */ break; } } @@ -258,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: * @@ -270,83 +569,140 @@ 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 - * if we can't. Presently, multiple entries are only allowed for UPDATE of - * an array field, for example "UPDATE table SET foo[2] = 42, foo[4] = 43". + * 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; * We can merge such operations into a single assignment op. Essentially, * 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 + * order of the original tlist's non-junk entries. This is needed for + * processing VALUES RTEs. */ static void -rewriteTargetList(Query *parsetree, Relation target_relation) +rewriteTargetListIU(Query *parsetree, Relation target_relation, + List **attrno_list) { CmdType commandType = parsetree->commandType; - List *tlist = parsetree->targetList; + TargetEntry **new_tles; List *new_tlist = NIL; + List *junk_tlist = NIL; + Form_pg_attribute att_tup; int attrno, + next_junk_attrno, numattrs; - List *temp; + ListCell *temp; + + if (attrno_list) /* initialize optional result list */ + *attrno_list = NIL; /* - * Scan the tuple description in the relation's relcache entry to make - * sure we have all the user attributes in the right order. + * We process the normal (non-junk) attributes by scanning the input tlist + * once and transferring TLEs into an array, then scanning the array to + * build an output tlist. This avoids O(N^2) behavior for large numbers + * of attributes. + * + * Junk attributes are tossed into a separate list during the same tlist + * scan, then appended to the reconstructed tlist. */ numattrs = RelationGetNumberOfAttributes(target_relation); + new_tles = (TargetEntry **) palloc0(numattrs * sizeof(TargetEntry *)); + next_junk_attrno = numattrs + 1; - for (attrno = 1; attrno <= numattrs; attrno++) + foreach(temp, parsetree->targetList) { - Form_pg_attribute att_tup = target_relation->rd_att->attrs[attrno - 1]; - TargetEntry *new_tle = NULL; + TargetEntry *old_tle = (TargetEntry *) lfirst(temp); - /* We can ignore deleted attributes */ - if (att_tup->attisdropped) - continue; + if (!old_tle->resjunk) + { + /* Normal attr: stash it into new_tles[] */ + attrno = old_tle->resno; + if (attrno < 1 || attrno > numattrs) + elog(ERROR, "bogus resno %d in targetlist", attrno); + att_tup = target_relation->rd_att->attrs[attrno - 1]; + + /* put attrno into attrno_list even if it's dropped */ + if (attrno_list) + *attrno_list = lappend_int(*attrno_list, attrno); + + /* We can (and must) ignore deleted attributes */ + if (att_tup->attisdropped) + continue; - /* - * Look for targetlist entries matching this attr. - * - * Junk attributes are not candidates to be matched. - */ - foreach(temp, tlist) + /* Merge with any prior assignment to same attribute */ + new_tles[attrno - 1] = + process_matched_tle(old_tle, + new_tles[attrno - 1], + NameStr(att_tup->attname)); + } + else { - TargetEntry *old_tle = (TargetEntry *) lfirst(temp); - Resdom *resdom = old_tle->resdom; + /* + * Copy all resjunk tlist entries to junk_tlist, and assign them + * resnos above the last real resno. + * + * Typical junk entries include ORDER BY or GROUP BY expressions + * (are these actually possible in an INSERT or UPDATE?), system + * attribute references, etc. + */ - if (!resdom->resjunk && resdom->resno == attrno) + /* Get the resno right, but don't copy unnecessarily */ + if (old_tle->resno != next_junk_attrno) { - new_tle = process_matched_tle(old_tle, new_tle, - NameStr(att_tup->attname)); - /* keep scanning to detect multiple assignments to attr */ + old_tle = flatCopyTargetEntry(old_tle); + old_tle->resno = next_junk_attrno; } + junk_tlist = lappend(junk_tlist, old_tle); + next_junk_attrno++; } + } + + for (attrno = 1; attrno <= numattrs; attrno++) + { + TargetEntry *new_tle = new_tles[attrno - 1]; + + att_tup = target_relation->rd_att->attrs[attrno - 1]; + + /* We can (and must) ignore deleted attributes */ + if (att_tup->attisdropped) + continue; /* - * Handle the two cases where we need to insert a default - * expression: it's an INSERT and there's no tlist entry for the - * column, or the tlist entry is a DEFAULT placeholder node. + * Handle the two cases where we need to insert a default expression: + * it's an INSERT and there's no tlist entry for the column, or the + * tlist entry is a DEFAULT placeholder node. */ if ((new_tle == NULL && commandType == CMD_INSERT) || - (new_tle && new_tle->expr && IsA(new_tle->expr, SetToDefault))) + (new_tle && new_tle->expr && IsA(new_tle->expr, SetToDefault))) { Node *new_expr; new_expr = build_column_default(target_relation, attrno); /* - * If there is no default (ie, default is effectively NULL), - * we can omit the tlist entry in the INSERT case, since the - * planner can insert a NULL for itself, and there's no point - * in spending any more rewriter cycles on the entry. But in - * the UPDATE case we've got to explicitly set the column to - * NULL. + * If there is no default (ie, default is effectively NULL), we + * can omit the tlist entry in the INSERT case, since the planner + * can insert a NULL for itself, and there's no point in spending + * any more rewriter cycles on the entry. But in the UPDATE case + * we've got to explicitly set the column to NULL. */ if (!new_expr) { @@ -355,65 +711,58 @@ rewriteTargetList(Query *parsetree, Relation target_relation) else { new_expr = (Node *) makeConst(att_tup->atttypid, + -1, att_tup->attlen, (Datum) 0, true, /* isnull */ att_tup->attbyval); /* this is to catch a NOT NULL domain constraint */ new_expr = coerce_to_domain(new_expr, - InvalidOid, + InvalidOid, -1, att_tup->atttypid, - COERCE_IMPLICIT_CAST); + COERCE_IMPLICIT_CAST, + -1, + false, + false); } } if (new_expr) - new_tle = makeTargetEntry(makeResdom(attrno, - att_tup->atttypid, - att_tup->atttypmod, + new_tle = makeTargetEntry((Expr *) new_expr, + attrno, + pstrdup(NameStr(att_tup->attname)), + 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), - (Expr *) new_expr); + false); } if (new_tle) new_tlist = lappend(new_tlist, new_tle); } - /* - * Copy all resjunk tlist entries to the end of the new tlist, and - * assign them resnos above the last real resno. - * - * Typical junk entries include ORDER BY or GROUP BY expressions (are - * these actually possible in an INSERT or UPDATE?), system attribute - * references, etc. - */ - foreach(temp, tlist) - { - TargetEntry *old_tle = (TargetEntry *) lfirst(temp); - Resdom *resdom = old_tle->resdom; + pfree(new_tles); - if (resdom->resjunk) - { - /* Get the resno right, but don't copy unnecessarily */ - if (resdom->resno != attrno) - { - resdom = (Resdom *) copyObject((Node *) resdom); - resdom->resno = attrno; - old_tle = makeTargetEntry(resdom, old_tle->expr); - } - new_tlist = lappend(new_tlist, old_tle); - attrno++; - } - else - { - /* Let's just make sure we processed all the non-junk items */ - if (resdom->resno < 1 || resdom->resno > numattrs) - elog(ERROR, "bogus resno %d in targetlist", resdom->resno); - } - } - - parsetree->targetList = new_tlist; + parsetree->targetList = list_concat(new_tlist, junk_tlist); } @@ -428,43 +777,70 @@ process_matched_tle(TargetEntry *src_tle, TargetEntry *prior_tle, const char *attrName) { - Resdom *resdom = src_tle->resdom; + TargetEntry *result; + Node *src_expr; + Node *prior_expr; + Node *src_input; + Node *prior_input; Node *priorbottom; - ArrayRef *newexpr; + Node *newexpr; if (prior_tle == NULL) { /* - * Normal case where this is the first assignment to the - * attribute. + * Normal case where this is the first assignment to the attribute. */ return src_tle; } - /* + /*---------- * Multiple assignments to same attribute. Allow only if all are - * array-assign operators with same bottom array object. + * FieldStore or ArrayRef assignment operations. This is a bit + * tricky because what we may actually be looking at is a nest of + * such nodes; consider + * UPDATE tab SET col.fld1.subfld1 = x, col.fld2.subfld2 = y + * The two expressions produced by the parser will look like + * FieldStore(col, fld1, FieldStore(placeholder, subfld1, x)) + * FieldStore(col, fld2, FieldStore(placeholder, subfld2, x)) + * However, we can ignore the substructure and just consider the top + * FieldStore or ArrayRef from each assignment, because it works to + * combine these as + * FieldStore(FieldStore(col, fld1, + * FieldStore(placeholder, subfld1, x)), + * fld2, FieldStore(placeholder, subfld2, x)) + * Note the leftmost expression goes on the inside so that the + * assignments appear to occur left-to-right. + * + * For FieldStore, instead of nesting we can generate a single + * FieldStore with multiple target fields. We must nest when + * ArrayRefs are involved though. + *---------- */ - if (src_tle->expr == NULL || !IsA(src_tle->expr, ArrayRef) || - ((ArrayRef *) src_tle->expr)->refassgnexpr == NULL || - prior_tle->expr == NULL || !IsA(prior_tle->expr, ArrayRef) || - ((ArrayRef *) prior_tle->expr)->refassgnexpr == NULL || - ((ArrayRef *) src_tle->expr)->refrestype != - ((ArrayRef *) prior_tle->expr)->refrestype) + src_expr = (Node *) src_tle->expr; + prior_expr = (Node *) prior_tle->expr; + src_input = get_assignment_input(src_expr); + prior_input = get_assignment_input(prior_expr); + if (src_input == NULL || + prior_input == NULL || + exprType(src_expr) != exprType(prior_expr)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("multiple assignments to same column \"%s\"", attrName))); /* - * Prior TLE could be a nest of ArrayRefs if we do this more than - * once. + * Prior TLE could be a nest of assignments if we do this more than once. */ - priorbottom = (Node *) ((ArrayRef *) prior_tle->expr)->refexpr; - while (priorbottom != NULL && IsA(priorbottom, ArrayRef) && - ((ArrayRef *) priorbottom)->refassgnexpr != NULL) - priorbottom = (Node *) ((ArrayRef *) priorbottom)->refexpr; - if (!equal(priorbottom, ((ArrayRef *) src_tle->expr)->refexpr)) + priorbottom = prior_input; + for (;;) + { + Node *newbottom = get_assignment_input(priorbottom); + + if (newbottom == NULL) + break; /* found the original Var reference */ + priorbottom = newbottom; + } + if (!equal(priorbottom, src_input)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("multiple assignments to same column \"%s\"", @@ -473,13 +849,72 @@ process_matched_tle(TargetEntry *src_tle, /* * Looks OK to nest 'em. */ - newexpr = makeNode(ArrayRef); - memcpy(newexpr, src_tle->expr, sizeof(ArrayRef)); - newexpr->refexpr = prior_tle->expr; + if (IsA(src_expr, FieldStore)) + { + FieldStore *fstore = makeNode(FieldStore); - return makeTargetEntry(resdom, (Expr *) newexpr); + if (IsA(prior_expr, FieldStore)) + { + /* combine the two */ + memcpy(fstore, prior_expr, sizeof(FieldStore)); + fstore->newvals = + list_concat(list_copy(((FieldStore *) prior_expr)->newvals), + list_copy(((FieldStore *) src_expr)->newvals)); + fstore->fieldnums = + list_concat(list_copy(((FieldStore *) prior_expr)->fieldnums), + list_copy(((FieldStore *) src_expr)->fieldnums)); + } + else + { + /* general case, just nest 'em */ + memcpy(fstore, src_expr, sizeof(FieldStore)); + fstore->arg = (Expr *) prior_expr; + } + newexpr = (Node *) fstore; + } + else if (IsA(src_expr, ArrayRef)) + { + ArrayRef *aref = makeNode(ArrayRef); + + memcpy(aref, src_expr, sizeof(ArrayRef)); + aref->refexpr = (Expr *) prior_expr; + newexpr = (Node *) aref; + } + else + { + elog(ERROR, "cannot happen"); + newexpr = NULL; + } + + result = flatCopyTargetEntry(src_tle); + result->expr = (Expr *) newexpr; + return result; } +/* + * If node is an assignment node, return its input; else return NULL + */ +static Node * +get_assignment_input(Node *node) +{ + if (node == NULL) + return NULL; + if (IsA(node, FieldStore)) + { + FieldStore *fstore = (FieldStore *) node; + + return (Node *) fstore->arg; + } + else if (IsA(node, ArrayRef)) + { + ArrayRef *aref = (ArrayRef *) node; + + if (aref->refassgnexpr == NULL) + return NULL; + return (Node *) aref->refexpr; + } + return NULL; +} /* * Make an expression tree for the default value for a column. @@ -520,8 +955,7 @@ build_column_default(Relation rel, int attrno) if (expr == NULL) { /* - * No per-column default, so look for a default for the type - * itself. + * No per-column default, so look for a default for the type itself. */ expr = get_typdefault(atttype); } @@ -532,9 +966,9 @@ build_column_default(Relation rel, int attrno) /* * Make sure the value is coerced to the target column type; this will * generally be true already, but there seem to be some corner cases - * involving domain defaults where it might not be true. This should - * match the parser's processing of non-defaulted expressions --- see - * updateTargetListEntry(). + * involving domain defaults where it might not be true. This should match + * the parser's processing of non-defaulted expressions --- see + * transformAssignedExpr(). */ exprtype = exprType(expr); @@ -542,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), @@ -551,12 +986,175 @@ build_column_default(Relation rel, int attrno) NameStr(att_tup->attname), format_type_be(atttype), format_type_be(exprtype)), - errhint("You will need to rewrite or cast the expression."))); + errhint("You will need to rewrite or cast the expression."))); return expr; } +/* Does VALUES RTE contain any SetToDefault items? */ +static bool +searchForDefault(RangeTblEntry *rte) +{ + ListCell *lc; + + foreach(lc, rte->values_lists) + { + List *sublist = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, sublist) + { + Node *col = (Node *) lfirst(lc2); + + if (IsA(col, SetToDefault)) + return true; + } + } + return false; +} + +/* + * 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 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 + * referencing the VALUES RTE, and therefore process_matched_tle() will + * reject any such attempt with "multiple assignments to same column". + */ +static void +rewriteValuesRTE(RangeTblEntry *rte, Relation target_relation, List *attrnos) +{ + List *newValues; + ListCell *lc; + + /* + * Rebuilding all the lists is a pretty expensive proposition in a big + * VALUES list, and it's a waste of time if there aren't any DEFAULT + * placeholders. So first scan to see if there are any. + */ + if (!searchForDefault(rte)) + return; /* nothing to do */ + + /* Check list lengths (we can assume all the VALUES sublists are alike) */ + Assert(list_length(attrnos) == list_length(linitial(rte->values_lists))); + + newValues = NIL; + foreach(lc, rte->values_lists) + { + List *sublist = (List *) lfirst(lc); + List *newList = NIL; + ListCell *lc2; + ListCell *lc3; + + forboth(lc2, sublist, lc3, attrnos) + { + Node *col = (Node *) lfirst(lc2); + int attrno = lfirst_int(lc3); + + if (IsA(col, SetToDefault)) + { + Form_pg_attribute att_tup; + Node *new_expr; + + att_tup = target_relation->rd_att->attrs[attrno - 1]; + + if (!att_tup->attisdropped) + new_expr = build_column_default(target_relation, attrno); + else + new_expr = NULL; /* force a NULL if dropped */ + + /* + * If there is no default (ie, default is effectively NULL), + * we've got to explicitly set the column to NULL. + */ + if (!new_expr) + { + new_expr = (Node *) makeConst(att_tup->atttypid, + -1, + att_tup->attlen, + (Datum) 0, + true, /* isnull */ + att_tup->attbyval); + /* this is to catch a NOT NULL domain constraint */ + new_expr = coerce_to_domain(new_expr, + InvalidOid, -1, + att_tup->atttypid, + COERCE_IMPLICIT_CAST, + -1, + false, + false); + } + newList = lappend(newList, new_expr); + } + else + newList = lappend(newList, col); + } + newValues = lappend(newValues, newList); + } + rte->values_lists = newValues; +} + + +/* + * 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 @@ -586,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 || @@ -601,37 +1221,114 @@ matchLocks(CmdType event, } +/* + * ApplyRetrieveRule - expand an ON SELECT rule + */ static Query * ApplyRetrieveRule(Query *parsetree, RewriteRule *rule, int rt_index, bool relation_level, Relation relation, - bool relIsUsed, - List *activeRIRs) + List *activeRIRs, + bool forUpdatePushedDown) { Query *rule_action; RangeTblEntry *rte, *subrte; + RowMarkClause *rc; - if (length(rule->actions) != 1) + if (list_length(rule->actions) != 1) elog(ERROR, "expected just one rule action"); if (rule->qual != NULL) elog(ERROR, "cannot handle qualified ON SELECT rule"); 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 recursively expand - * any view references inside it. + * Make a modifiable copy of the view query, and acquire needed locks on + * the relations it mentions. */ - rule_action = copyObject(lfirst(rule->actions)); + rule_action = copyObject(linitial(rule->actions)); - rule_action = fireRIRrules(rule_action, activeRIRs); + AcquireRewriteLocks(rule_action, forUpdatePushedDown); + + /* + * Recursively expand any view references inside the view. + */ + 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); @@ -641,75 +1338,94 @@ ApplyRetrieveRule(Query *parsetree, rte->inh = false; /* must not be set for a subquery */ /* - * We move the view's permission check data down to its rangetable. - * The checks will actually be done against the *OLD* entry therein. + * We move the view's permission check data down to its rangetable. The + * 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 = 0; + rte->checkAsUser = InvalidOid; + rte->selectedCols = NULL; + rte->modifiedCols = NULL; /* - * FOR UPDATE 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 (intMember(rt_index, parsetree->rowMarks)) - { - /* - * Remove the view from the list of rels that will actually be - * marked FOR UPDATE by the executor. It will still be access- - * checked for write access, though. - */ - parsetree->rowMarks = lremovei(rt_index, parsetree->rowMarks); - - /* - * Set up the view's referenced tables as if FOR UPDATE. - */ - markQueryForUpdate(rule_action, true); - } + if (rc != NULL) + markQueryForLocking(rule_action, (Node *) rule_action->jointree, + rc->forUpdate, rc->noWait, true); return parsetree; } /* - * Recursively mark all relations used by a view as FOR UPDATE. + * Recursively mark all relations used by a view as FOR UPDATE/SHARE. * * This may generate an invalid query, eg if some sub-query uses an * aggregate. We leave it to the planner to detect that. * - * NB: this must agree with the parser's transformForUpdate() routine. + * NB: this must agree with the parser's transformLockingClause() routine. + * However, unlike the parser we have to be careful not to mark a view's + * OLD and NEW rels for updating. The best way to handle that seems to be + * to scan the jointree to determine which rels are used. */ static void -markQueryForUpdate(Query *qry, bool skipOldNew) +markQueryForLocking(Query *qry, Node *jtnode, + bool forUpdate, bool noWait, bool pushedDown) { - Index rti = 0; - List *l; - - foreach(l, qry->rtable) + if (jtnode == NULL) + return; + if (IsA(jtnode, RangeTblRef)) { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(l); - - rti++; - - /* Ignore OLD and NEW entries if we are at top level of view */ - if (skipOldNew && - (rti == PRS2_OLD_VARNO || rti == PRS2_NEW_VARNO)) - continue; + int rti = ((RangeTblRef *) jtnode)->rtindex; + RangeTblEntry *rte = rt_fetch(rti, qry->rtable); if (rte->rtekind == RTE_RELATION) { - if (!intMember(rti, qry->rowMarks)) - qry->rowMarks = lappendi(qry->rowMarks, rti); - 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) { - /* FOR UPDATE of subquery is propagated to subquery's rels */ - markQueryForUpdate(rte->subquery, false); + 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, true); } + /* other RTE types are unaffected by FOR UPDATE */ + } + else if (IsA(jtnode, FromExpr)) + { + FromExpr *f = (FromExpr *) jtnode; + ListCell *l; + + foreach(l, f->fromlist) + markQueryForLocking(qry, lfirst(l), forUpdate, noWait, pushedDown); } + else if (IsA(jtnode, JoinExpr)) + { + JoinExpr *j = (JoinExpr *) jtnode; + + markQueryForLocking(qry, j->larg, forUpdate, noWait, pushedDown); + markQueryForLocking(qry, j->rarg, forUpdate, noWait, pushedDown); + } + else + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(jtnode)); } @@ -737,13 +1453,13 @@ 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 */ } /* - * Do NOT recurse into Query nodes, because fireRIRrules already - * processed subselects of subselects for us. + * Do NOT recurse into Query nodes, because fireRIRrules already processed + * subselects of subselects for us. */ return expression_tree_walker(node, fireRIRonSubLink, (void *) activeRIRs); @@ -755,24 +1471,24 @@ 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 get changed each time through... + * don't try to convert this into a foreach loop, because rtable list can + * get changed each time through... */ rt_index = 0; - while (rt_index < length(parsetree->rtable)) + while (rt_index < list_length(parsetree->rtable)) { RangeTblEntry *rte; Relation rel; List *locks; RuleLock *rules; RewriteRule *rule; - LOCKMODE lockmode; - bool relIsUsed; int i; ++rt_index; @@ -780,13 +1496,15 @@ fireRIRrules(Query *parsetree, List *activeRIRs) rte = rt_fetch(rt_index, parsetree->rtable); /* - * A subquery RTE can't have associated rules, so there's nothing - * to do to this level of the query, but we must recurse into the + * A subquery RTE can't have associated rules, so there's nothing to + * do to this level of the query, but we must recurse into the * subquery to expand any rule references in it. */ 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; } @@ -800,35 +1518,26 @@ fireRIRrules(Query *parsetree, List *activeRIRs) * If the table is not referenced in the query, then we ignore it. * This prevents infinite expansion loop due to new rtable entries * inserted by expansion of a rule. A table is referenced if it is - * part of the join set (a source table), or is referenced by any - * Var nodes, or is the result table. + * part of the join set (a source table), or is referenced by any Var + * nodes, or is the result table. */ - relIsUsed = rangeTableEntry_used((Node *) parsetree, rt_index, 0); - - if (!relIsUsed && rt_index != parsetree->resultRelation) + if (rt_index != parsetree->resultRelation && + !rangeTableEntry_used((Node *) parsetree, rt_index, 0)) continue; /* - * This may well be the first access to the relation during the - * current statement (it will be, if this Query was extracted from - * a rule or somehow got here other than via the parser). - * Therefore, grab the appropriate lock type for the relation, and - * do not release it until end of transaction. This protects the - * rewriter and planner against schema changes mid-query. - * - * If the relation is the query's result relation, then - * RewriteQuery() already got the right lock on it, so we need no - * additional lock. Otherwise, check to see if the relation is - * accessed FOR UPDATE or not. + * 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) - lockmode = NoLock; - else if (intMember(rt_index, parsetree->rowMarks)) - lockmode = RowShareLock; - else - lockmode = AccessShareLock; + if (rt_index == parsetree->resultRelation && + rt_index != origResultRelation) + continue; - rel = heap_open(rte->relid, lockmode); + /* + * We can use NoLock here since either the parser or + * AcquireRewriteLocks should have locked the rel already. + */ + rel = heap_open(rte->relid, NoLock); /* * Collect the RIR rules that we must apply @@ -862,15 +1571,14 @@ fireRIRrules(Query *parsetree, List *activeRIRs) */ if (locks != NIL) { - List *newActiveRIRs; - List *l; + ListCell *l; - if (oidMember(RelationGetRelid(rel), activeRIRs)) + if (list_member_oid(activeRIRs, RelationGetRelid(rel))) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("infinite recursion detected in rules for relation \"%s\"", RelationGetRelationName(rel)))); - newActiveRIRs = lconso(RelationGetRelid(rel), activeRIRs); + activeRIRs = lcons_oid(RelationGetRelid(rel), activeRIRs); foreach(l, locks) { @@ -881,38 +1589,32 @@ fireRIRrules(Query *parsetree, List *activeRIRs) rt_index, rule->attrno == -1, rel, - relIsUsed, - newActiveRIRs); + activeRIRs, + forUpdatePushedDown); } + + activeRIRs = list_delete_first(activeRIRs); } heap_close(rel, NoLock); } - /* - * Recurse into sublink subqueries, too. But we already did the ones - * in the rtable. - */ - if (parsetree->hasSubLinks) - query_tree_walker(parsetree, fireRIRonSubLink, (void *) activeRIRs, - QTW_IGNORE_RT_SUBQUERIES); + /* Recurse into subqueries in WITH */ + foreach(lc, parsetree->cteList) + { + CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc); + + cte->ctequery = (Node *) + fireRIRrules((Query *) cte->ctequery, activeRIRs, false); + } /* - * If the query was marked having aggregates, check if this is still - * true after rewriting. Ditto for sublinks. Note there should be no - * aggs in the qual at this point. (Does this code still do anything - * useful? The view-becomes-subselect-in-FROM approach doesn't look - * like it could remove aggs or sublinks...) + * Recurse into sublink subqueries, too. But we already did the ones in + * the rtable and cteList. */ - if (parsetree->hasAggs) - { - parsetree->hasAggs = checkExprHasAggs((Node *) parsetree); - if (parsetree->hasAggs) - if (checkExprHasAggs((Node *) parsetree->jointree)) - elog(ERROR, "failed to remove aggregates from qual"); - } if (parsetree->hasSubLinks) - parsetree->hasSubLinks = checkExprHasSubLink((Node *) parsetree); + query_tree_walker(parsetree, fireRIRonSubLink, (void *) activeRIRs, + QTW_IGNORE_RC_SUBQUERIES); return parsetree; } @@ -937,9 +1639,17 @@ CopyAndAddInvertedQual(Query *parsetree, int rt_index, CmdType event) { - Query *new_tree = (Query *) copyObject(parsetree); + /* Don't scribble on the passed qual (it's in the relcache!) */ Node *new_qual = (Node *) copyObject(rule_qual); + /* + * In case there are subqueries in the qual, acquire necessary locks and + * fix any deleted JOIN RTE entries. (This is somewhat redundant with + * rewriteRuleAction, but not entirely ... consider restructuring so that + * we only need to process the qual this way once.) + */ + (void) acquireLocksOnSubLinks(new_qual, NULL); + /* Fix references to OLD */ ChangeVarNodes(new_qual, PRS2_OLD_VARNO, rt_index, 0); /* Fix references to NEW */ @@ -947,13 +1657,15 @@ CopyAndAddInvertedQual(Query *parsetree, new_qual = ResolveNew(new_qual, PRS2_NEW_VARNO, 0, + rt_fetch(rt_index, parsetree->rtable), parsetree->targetList, event, - rt_index); + rt_index, + &parsetree->hasSubLinks); /* And attach the fixed qual */ - AddInvertedQual(new_tree, new_qual); + AddInvertedQual(parsetree, new_qual); - return new_tree; + return parsetree; } @@ -969,6 +1681,8 @@ CopyAndAddInvertedQual(Query *parsetree, * Output arguments: * *instead_flag - set TRUE if any unqualified INSTEAD rule is found * (must be initialized to FALSE) + * *returning_flag - set TRUE if we rewrite RETURNING clause in any rule + * (must be initialized to FALSE) * *qual_product - filled with modified original query if any qualified * INSTEAD rule is found (must be initialized to NULL) * Return value: @@ -989,18 +1703,19 @@ fireRules(Query *parsetree, CmdType event, List *locks, bool *instead_flag, + bool *returning_flag, Query **qual_product) { List *results = NIL; - List *i; + ListCell *l; - foreach(i, locks) + foreach(l, locks) { - RewriteRule *rule_lock = (RewriteRule *) lfirst(i); + RewriteRule *rule_lock = (RewriteRule *) lfirst(l); Node *event_qual = rule_lock->qual; List *actions = rule_lock->actions; QuerySource qsrc; - List *r; + ListCell *r; /* Determine correct QuerySource value for actions */ if (rule_lock->isInstead) @@ -1019,13 +1734,13 @@ fireRules(Query *parsetree, if (qsrc == QSRC_QUAL_INSTEAD_RULE) { /* - * If there are INSTEAD rules with qualifications, the - * original query is still performed. But all the negated rule - * qualifications of the INSTEAD rules are added so it does - * its actions only in cases where the rule quals of all - * INSTEAD rules are false. Think of it as the default action - * in a case. We save this in *qual_product so RewriteQuery() - * can add it to the query list after we mangled it up enough. + * If there are INSTEAD rules with qualifications, the original + * query is still performed. But all the negated rule + * qualifications of the INSTEAD rules are added so it does its + * actions only in cases where the rule quals of all INSTEAD rules + * are false. Think of it as the default action in a case. We save + * this in *qual_product so RewriteQuery() can add it to the query + * list after we mangled it up enough. * * If we have already found an unqualified INSTEAD rule, then * *qual_product won't be used, so don't bother building it. @@ -1033,7 +1748,7 @@ fireRules(Query *parsetree, if (!*instead_flag) { if (*qual_product == NULL) - *qual_product = parsetree; + *qual_product = copyObject(parsetree); *qual_product = CopyAndAddInvertedQual(*qual_product, event_qual, rt_index, @@ -1050,7 +1765,8 @@ fireRules(Query *parsetree, continue; rule_action = rewriteRuleAction(parsetree, rule_action, - event_qual, rt_index, event); + event_qual, rt_index, event, + returning_flag); rule_action->querySource = qsrc; rule_action->canSetTag = false; /* might change later */ @@ -1075,15 +1791,17 @@ RewriteQuery(Query *parsetree, List *rewrite_events) { CmdType event = parsetree->commandType; bool instead = false; + bool returning = false; Query *qual_product = NULL; 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 need that check?) + * 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 + * need that check?) */ if (event != CMD_SELECT && event != CMD_UTILITY) { @@ -1098,24 +1816,62 @@ RewriteQuery(Query *parsetree, List *rewrite_events) Assert(rt_entry->rtekind == RTE_RELATION); /* - * This may well be the first access to the result relation during - * the current statement (it will be, if this Query was extracted - * from a rule or somehow got here other than via the parser). - * Therefore, grab the appropriate lock type for a result - * relation, and do not release it until end of transaction. This - * protects the rewriter and planner against schema changes - * mid-query. + * We can use NoLock here since either the parser or + * AcquireRewriteLocks should have locked the rel already. */ - rt_entry_relation = heap_open(rt_entry->relid, RowExclusiveLock); + 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_INSERT || event == CMD_UPDATE) - rewriteTargetList(parsetree, rt_entry_relation); + if (event == CMD_INSERT) + { + RangeTblEntry *values_rte = NULL; + + /* + * If it's an INSERT ... VALUES (...), (...), ... there will be a + * single RTE for the VALUES targetlists. + */ + if (list_length(parsetree->jointree->fromlist) == 1) + { + RangeTblRef *rtr = (RangeTblRef *) linitial(parsetree->jointree->fromlist); + + if (IsA(rtr, RangeTblRef)) + { + RangeTblEntry *rte = rt_fetch(rtr->rtindex, + parsetree->rtable); + + if (rte->rtekind == RTE_VALUES) + values_rte = rte; + } + } + + if (values_rte) + { + List *attrnos; + + /* Process the main targetlist ... */ + rewriteTargetListIU(parsetree, rt_entry_relation, &attrnos); + /* ... and the VALUES expression lists */ + rewriteValuesRTE(values_rte, rt_entry_relation, attrnos); + } + else + { + /* Process just the main targetlist */ + 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. @@ -1132,15 +1888,16 @@ RewriteQuery(Query *parsetree, List *rewrite_events) event, locks, &instead, + &returning, &qual_product); /* - * If we got any product queries, recursively rewrite them --- - * but first check for recursion! + * If we got any product queries, recursively rewrite them --- but + * first check for recursion! */ if (product_queries != NIL) { - List *n; + ListCell *n; rewrite_event *rev; foreach(n, rewrite_events) @@ -1149,9 +1906,9 @@ RewriteQuery(Query *parsetree, List *rewrite_events) if (rev->relation == RelationGetRelid(rt_entry_relation) && rev->event == event) ereport(ERROR, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("infinite recursion detected in rules for relation \"%s\"", - RelationGetRelationName(rt_entry_relation)))); + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("infinite recursion detected in rules for relation \"%s\"", + RelationGetRelationName(rt_entry_relation)))); } rev = (rewrite_event *) palloc(sizeof(rewrite_event)); @@ -1165,22 +1922,64 @@ RewriteQuery(Query *parsetree, List *rewrite_events) List *newstuff; newstuff = RewriteQuery(pt, rewrite_events); - rewritten = nconc(rewritten, newstuff); + rewritten = list_concat(rewritten, newstuff); } + + rewrite_events = list_delete_first(rewrite_events); + } + } + + /* + * If there is an INSTEAD, and the original query has a RETURNING, we + * have to have found a RETURNING in the rule(s), else fail. (Because + * DefineQueryRewrite only allows RETURNING in unconditional INSTEAD + * rules, there's no need to worry whether the substituted RETURNING + * will actually be executed --- it must be.) + */ + if ((instead || qual_product != NULL) && + parsetree->returningList && + !returning) + { + switch (event) + { + case CMD_INSERT: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + 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)), + 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)), + errhint("You need an unconditional ON DELETE DO INSTEAD rule with a RETURNING clause."))); + break; + default: + elog(ERROR, "unrecognized commandType: %d", + (int) event); + break; } } - heap_close(rt_entry_relation, NoLock); /* keep lock! */ + heap_close(rt_entry_relation, NoLock); } /* - * For INSERTs, the original query is done first; for UPDATE/DELETE, - * it is done last. This is needed because update and delete rule - * actions might not do anything if they are invoked after the update - * or delete is performed. The command counter increment between the - * query executions makes the deleted (and maybe the updated) tuples - * disappear so the scans for them in the rule actions cannot find - * them. + * For INSERTs, the original query is done first; for UPDATE/DELETE, it is + * done last. This is needed because update and delete rule actions might + * not do anything if they are invoked after the update or delete is + * performed. The command counter increment between the query executions + * makes the deleted (and maybe the updated) tuples disappear so the scans + * for them in the rule actions cannot find them. * * If we found any unqualified INSTEAD, the original query is not done at * all, in any form. Otherwise, we add the modified form if qualified @@ -1214,15 +2013,15 @@ RewriteQuery(Query *parsetree, List *rewrite_events) * Rewrite one query via query rewrite system, possibly returning 0 * or many queries. * - * NOTE: The code in QueryRewrite was formerly in pg_parse_and_plan(), and was - * moved here so that it would be invoked during EXPLAIN. + * NOTE: the parsetree must either have come straight from the parser, + * or have been scanned by AcquireRewriteLocks to acquire suitable locks. */ List * QueryRewrite(Query *parsetree) { List *querylist; - List *results = NIL; - List *l; + List *results; + ListCell *l; CmdType origCmdType; bool foundOriginalQuery; Query *lastInstead; @@ -1239,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); } @@ -1290,18 +2051,17 @@ QueryRewrite(Query *parsetree) * Step 3 * * Determine which, if any, of the resulting queries is supposed to set - * the command-result tag; and update the canSetTag fields - * accordingly. + * the command-result tag; and update the canSetTag fields accordingly. * * If the original query is still in the list, it sets the command tag. - * Otherwise, the last INSTEAD query of the same kind as the original - * is allowed to set the tag. (Note these rules can leave us with no - * query setting the tag. The tcop code has to cope with this by - * setting up a default tag based on the original un-rewritten query.) + * Otherwise, the last INSTEAD query of the same kind as the original is + * allowed to set the tag. (Note these rules can leave us with no query + * setting the tag. The tcop code has to cope with this by setting up a + * default tag based on the original un-rewritten query.) * * The Asserts verify that at most one query in the result list is marked - * canSetTag. If we aren't checking asserts, we can fall out of the - * loop as soon as we find the original query. + * canSetTag. If we aren't checking asserts, we can fall out of the loop + * as soon as we find the original query. */ origCmdType = parsetree->commandType; foundOriginalQuery = false;