* 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.137 2004/05/29 05:55:13 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 */
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;
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,
/*
* 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.
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,
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);
* 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,
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;
}
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;
}
}
/*
- * rewriteTargetList - rewrite INSERT/UPDATE targetlist into standard form
+ * rewriteTargetListIU - rewrite INSERT/UPDATE targetlist into standard form
*
* This has the following responsibilities:
*
* 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;
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)
{
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);
}
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\"",
/*
* 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.
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);
}
/*
* 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);
expr, exprtype,
atttype, atttypmod,
COERCION_ASSIGNMENT,
- COERCE_IMPLICIT_CAST);
+ COERCE_IMPLICIT_CAST,
+ -1);
if (expr == NULL)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
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
{
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 ||
}
+/*
+ * 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(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);
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;
- ListCell *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));
}
/* 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);
* 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;
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;
}
* 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
{
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))));
- activeRIRs = lconso(RelationGetRelid(rel), activeRIRs);
+ activeRIRs = lcons_oid(RelationGetRelid(rel), activeRIRs);
foreach(l, locks)
{
rt_index,
rule->attrno == -1,
rel,
- relIsUsed,
- activeRIRs);
+ 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;
}
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 */
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;
}
* 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:
CmdType event,
List *locks,
bool *instead_flag,
+ bool *returning_flag,
Query **qual_product)
{
List *results = NIL;
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.
if (!*instead_flag)
{
if (*qual_product == NULL)
- *qual_product = parsetree;
+ *qual_product = copyObject(parsetree);
*qual_product = CopyAndAddInvertedQual(*qual_product,
event_qual,
rt_index,
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 */
{
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)
{
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.
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)
{
- ListCell *n;
- rewrite_event *rev;
+ ListCell *n;
+ rewrite_event *rev;
foreach(n, 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));
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
* 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 *results;
ListCell *l;
CmdType origCmdType;
bool foundOriginalQuery;
*
* 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);
}
* 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;