* rewriteHandler.c
* Primary module of query rewriter.
*
- * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.172 2007/03/17 00:11:04 tgl Exp $
+ * src/backend/rewrite/rewriteHandler.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/heapam.h"
+#include "access/sysattr.h"
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
-#include "optimizer/clauses.h"
+#include "nodes/nodeFuncs.h"
#include "parser/analyze.h"
#include "parser/parse_coerce.h"
-#include "parser/parse_expr.h"
#include "parser/parsetree.h"
+#include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
+#include "commands/trigger.h"
/* We use a list of these to detect recursion in RewriteQuery */
CmdType event,
bool *returning_flag);
static List *adjustJoinTreeList(Query *parsetree, bool removert, int rt_index);
-static void rewriteTargetList(Query *parsetree, Relation target_relation,
- List **attrno_list);
+static void rewriteTargetListIU(Query *parsetree, Relation target_relation,
+ List **attrno_list);
static TargetEntry *process_matched_tle(TargetEntry *src_tle,
TargetEntry *prior_tle,
const char *attrName);
static Node *get_assignment_input(Node *node);
static void rewriteValuesRTE(RangeTblEntry *rte, Relation target_relation,
List *attrnos);
+static void rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
+ Relation target_relation);
static void markQueryForLocking(Query *qry, Node *jtnode,
- bool forUpdate, bool noWait);
+ bool forUpdate, bool noWait, bool pushedDown);
static List *matchLocks(CmdType event, RuleLock *rulelocks,
int varno, Query *parsetree);
-static Query *fireRIRrules(Query *parsetree, List *activeRIRs);
+static Query *fireRIRrules(Query *parsetree, List *activeRIRs,
+ bool forUpdatePushedDown);
/*
* 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
* construction of a nested join was O(N^2) in the nesting depth.)
*/
void
-AcquireRewriteLocks(Query *parsetree)
+AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown)
{
ListCell *l;
int rt_index;
*/
if (rt_index == parsetree->resultRelation)
lockmode = RowExclusiveLock;
- else if (get_rowmark(parsetree, rt_index))
+ else if (forUpdatePushedDown ||
+ get_parse_rowmark(parsetree, rt_index) != NULL)
lockmode = RowShareLock;
else
lockmode = AccessShareLock;
* now-dropped type OID, but it doesn't really
* matter what type the Const claims to be.
*/
- aliasvar = (Var *) makeNullConst(INT4OID);
+ aliasvar = (Var *) makeNullConst(INT4OID, -1);
}
}
newaliasvars = lappend(newaliasvars, aliasvar);
* The subquery RTE itself is all right, but we have to
* recurse to process the represented subquery.
*/
- AcquireRewriteLocks(rte->subquery);
+ AcquireRewriteLocks(rte->subquery,
+ (forUpdatePushedDown ||
+ get_parse_rowmark(parsetree, rt_index) != NULL));
break;
default:
}
}
+ /* Recurse into subqueries in WITH */
+ foreach(l, parsetree->cteList)
+ {
+ CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
+
+ AcquireRewriteLocks((Query *) cte->ctequery, false);
+ }
+
/*
* Recurse into sublink subqueries, too. But we already did the ones in
- * the rtable.
+ * the rtable and cteList.
*/
if (parsetree->hasSubLinks)
query_tree_walker(parsetree, acquireLocksOnSubLinks, NULL,
- QTW_IGNORE_RT_SUBQUERIES);
+ QTW_IGNORE_RC_SUBQUERIES);
}
/*
SubLink *sub = (SubLink *) node;
/* Do what we came for */
- AcquireRewriteLocks((Query *) sub->subselect);
+ AcquireRewriteLocks((Query *) sub->subselect, false);
/* Fall through to process lefthand args of SubLink */
}
/*
* Acquire necessary locks and fix any deleted JOIN RTE entries.
*/
- AcquireRewriteLocks(rule_action);
+ AcquireRewriteLocks(rule_action, false);
(void) acquireLocksOnSubLinks(rule_qual, NULL);
current_varno = rt_index;
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,
sub_action->rtable = list_concat((List *) copyObject(parsetree->rtable),
sub_action->rtable);
+ /*
+ * There could have been some SubLinks in parsetree's rtable, in which
+ * case we'd better mark the sub_action correctly.
+ */
+ if (parsetree->hasSubLinks && !sub_action->hasSubLinks)
+ {
+ ListCell *lc;
+
+ foreach(lc, parsetree->rtable)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+
+ switch (rte->rtekind)
+ {
+ case RTE_FUNCTION:
+ sub_action->hasSubLinks =
+ checkExprHasSubLink(rte->funcexpr);
+ break;
+ case RTE_VALUES:
+ sub_action->hasSubLinks =
+ checkExprHasSubLink((Node *) rte->values_lists);
+ break;
+ default:
+ /* other RTE types don't contain bare expressions */
+ break;
+ }
+ if (sub_action->hasSubLinks)
+ break; /* no need to keep scanning rtable */
+ }
+ }
+
/*
* Each rule action's jointree should be the main parsetree's jointree
* plus that rule's jointree, but usually *without* the original rtindex
sub_action->rtable),
parsetree->targetList,
event,
- current_varno);
+ current_varno,
+ NULL);
if (sub_action_ptr)
*sub_action_ptr = sub_action;
else
parsetree->rtable),
rule_action->returningList,
CMD_SELECT,
- 0);
+ 0,
+ &rule_action->hasSubLinks);
+
+ /*
+ * There could have been some SubLinks in parsetree's returningList,
+ * in which case we'd better mark the rule_action correctly.
+ */
+ if (parsetree->hasSubLinks && !rule_action->hasSubLinks)
+ rule_action->hasSubLinks =
+ checkExprHasSubLink((Node *) rule_action->returningList);
}
return rule_action;
/*
- * 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
+ * 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;
* 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
* processing VALUES RTEs.
*/
static void
-rewriteTargetList(Query *parsetree, Relation target_relation,
- List **attrno_list)
+rewriteTargetListIU(Query *parsetree, Relation target_relation,
+ List **attrno_list)
{
CmdType commandType = parsetree->commandType;
TargetEntry **new_tles;
InvalidOid, -1,
att_tup->atttypid,
COERCE_IMPLICIT_CAST,
+ -1,
false,
false);
}
false);
}
+ /*
+ * For an UPDATE on a view, provide a dummy entry whenever there is
+ * no explicit assignment.
+ */
+ if (new_tle == NULL && commandType == CMD_UPDATE &&
+ target_relation->rd_rel->relkind == RELKIND_VIEW)
+ {
+ Node *new_expr;
+
+ new_expr = (Node *) makeVar(parsetree->resultRelation,
+ attrno,
+ att_tup->atttypid,
+ att_tup->atttypmod,
+ att_tup->attcollation,
+ 0);
+
+ new_tle = makeTargetEntry((Expr *) new_expr,
+ attrno,
+ pstrdup(NameStr(att_tup->attname)),
+ false);
+ }
+
if (new_tle)
new_tlist = lappend(new_tlist, new_tle);
}
expr, exprtype,
atttype, atttypmod,
COERCION_ASSIGNMENT,
- COERCE_IMPLICIT_CAST);
+ COERCE_IMPLICIT_CAST,
+ -1);
if (expr == NULL)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
/*
* When processing INSERT ... VALUES with a VALUES RTE (ie, multiple VALUES
* lists), we have to replace any DEFAULT items in the VALUES lists with
- * the appropriate default expressions. The other aspects of rewriteTargetList
- * need be applied only to the query's targetlist proper.
+ * the appropriate default expressions. The other aspects of targetlist
+ * rewriting need be applied only to the query's targetlist proper.
*
* Note that we currently can't support subscripted or field assignment
* in the multi-VALUES case. The targetlist will contain simple Vars
InvalidOid, -1,
att_tup->atttypid,
COERCE_IMPLICIT_CAST,
+ -1,
false,
false);
}
}
+/*
+ * 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 ||
int rt_index,
bool relation_level,
Relation relation,
- List *activeRIRs)
+ List *activeRIRs,
+ bool forUpdatePushedDown)
{
Query *rule_action;
RangeTblEntry *rte,
if (!relation_level)
elog(ERROR, "cannot handle per-attribute ON SELECT rule");
+ if (rt_index == parsetree->resultRelation)
+ {
+ /*
+ * We have a view as the result relation of the query, and it wasn't
+ * rewritten by any rule. This case is supported if there is an
+ * INSTEAD OF trigger that will trap attempts to insert/update/delete
+ * view rows. The executor will check that; for the moment just plow
+ * ahead. We have two cases:
+ *
+ * For INSERT, we needn't do anything. The unmodified RTE will serve
+ * fine as the result relation.
+ *
+ * For UPDATE/DELETE, we need to expand the view so as to have source
+ * data for the operation. But we also need an unmodified RTE to
+ * serve as the target. So, copy the RTE and add the copy to the
+ * rangetable. Note that the copy does not get added to the jointree.
+ * Also note that there's a hack in fireRIRrules to avoid calling
+ * this function again when it arrives at the copied RTE.
+ */
+ if (parsetree->commandType == CMD_INSERT)
+ return parsetree;
+ else if (parsetree->commandType == CMD_UPDATE ||
+ parsetree->commandType == CMD_DELETE)
+ {
+ RangeTblEntry *newrte;
+
+ rte = rt_fetch(rt_index, parsetree->rtable);
+ newrte = copyObject(rte);
+ parsetree->rtable = lappend(parsetree->rtable, newrte);
+ parsetree->resultRelation = list_length(parsetree->rtable);
+
+ /*
+ * There's no need to do permissions checks twice, so wipe out
+ * the permissions info for the original RTE (we prefer to keep
+ * the bits set on the result RTE).
+ */
+ rte->requiredPerms = 0;
+ rte->checkAsUser = InvalidOid;
+ rte->selectedCols = NULL;
+ rte->modifiedCols = NULL;
+
+ /*
+ * For the most part, Vars referencing the view should remain as
+ * they are, meaning that they implicitly represent OLD values.
+ * But in the RETURNING list if any, we want such Vars to
+ * represent NEW values, so change them to reference the new RTE.
+ *
+ * Since ChangeVarNodes scribbles on the tree in-place, copy the
+ * RETURNING list first for safety.
+ */
+ parsetree->returningList = copyObject(parsetree->returningList);
+ ChangeVarNodes((Node *) parsetree->returningList, rt_index,
+ parsetree->resultRelation, 0);
+
+ /* Now, continue with expanding the original view RTE */
+ }
+ else
+ elog(ERROR, "unrecognized commandType: %d",
+ (int) parsetree->commandType);
+ }
+
+ /*
+ * If FOR UPDATE/SHARE of view, be sure we get right initial lock on the
+ * relations it references.
+ */
+ rc = get_parse_rowmark(parsetree, rt_index);
+ forUpdatePushedDown |= (rc != NULL);
+
/*
* Make a modifiable copy of the view query, and acquire needed locks on
* the relations it mentions.
*/
rule_action = copyObject(linitial(rule->actions));
- AcquireRewriteLocks(rule_action);
+ AcquireRewriteLocks(rule_action, forUpdatePushedDown);
/*
* Recursively expand any view references inside the view.
*/
- rule_action = fireRIRrules(rule_action, activeRIRs);
+ rule_action = fireRIRrules(rule_action, activeRIRs, forUpdatePushedDown);
/*
- * VIEWs are really easy --- just plug the view query in as a subselect,
- * replacing the relation's original RTE.
+ * Now, plug the view query in as a subselect, replacing the relation's
+ * original RTE.
*/
rte = rt_fetch(rt_index, parsetree->rtable);
/*
* We move the view's permission check data down to its rangetable. The
- * checks will actually be done against the *OLD* entry therein.
+ * checks will actually be done against the OLD entry therein.
*/
subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
Assert(subrte->relid == relation->rd_id);
subrte->requiredPerms = rte->requiredPerms;
subrte->checkAsUser = rte->checkAsUser;
+ subrte->selectedCols = rte->selectedCols;
+ subrte->modifiedCols = rte->modifiedCols;
rte->requiredPerms = 0; /* no permission check on subquery itself */
rte->checkAsUser = InvalidOid;
+ rte->selectedCols = NULL;
+ rte->modifiedCols = NULL;
/*
- * FOR UPDATE/SHARE of view?
+ * If FOR UPDATE/SHARE of view, mark all the contained tables as implicit
+ * FOR UPDATE/SHARE, the same as the parser would have done if the view's
+ * subquery had been written out explicitly.
+ *
+ * Note: we don't consider forUpdatePushedDown here; such marks will be
+ * made by recursing from the upper level in markQueryForLocking.
*/
- if ((rc = get_rowmark(parsetree, rt_index)) != NULL)
- {
- /*
- * Remove the view from the list of rels that will actually be marked
- * FOR UPDATE/SHARE by the executor. It will still be access-checked
- * for write access, though.
- */
- parsetree->rowMarks = list_delete_ptr(parsetree->rowMarks, rc);
-
- /*
- * Set up the view's referenced tables as if FOR UPDATE/SHARE.
- */
+ if (rc != NULL)
markQueryForLocking(rule_action, (Node *) rule_action->jointree,
- rc->forUpdate, rc->noWait);
- }
+ rc->forUpdate, rc->noWait, true);
return parsetree;
}
* to scan the jointree to determine which rels are used.
*/
static void
-markQueryForLocking(Query *qry, Node *jtnode, bool forUpdate, bool noWait)
+markQueryForLocking(Query *qry, Node *jtnode,
+ bool forUpdate, bool noWait, bool pushedDown)
{
if (jtnode == NULL)
return;
if (rte->rtekind == RTE_RELATION)
{
- applyLockingClause(qry, rti, forUpdate, noWait);
- rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+ /* ignore foreign tables */
+ if (get_rel_relkind(rte->relid) != RELKIND_FOREIGN_TABLE)
+ {
+ applyLockingClause(qry, rti, forUpdate, noWait, pushedDown);
+ rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+ }
}
else if (rte->rtekind == RTE_SUBQUERY)
{
+ applyLockingClause(qry, rti, forUpdate, noWait, pushedDown);
/* FOR UPDATE/SHARE of subquery is propagated to subquery's rels */
markQueryForLocking(rte->subquery, (Node *) rte->subquery->jointree,
- forUpdate, noWait);
+ forUpdate, noWait, true);
}
+ /* other RTE types are unaffected by FOR UPDATE */
}
else if (IsA(jtnode, FromExpr))
{
ListCell *l;
foreach(l, f->fromlist)
- markQueryForLocking(qry, lfirst(l), forUpdate, noWait);
+ markQueryForLocking(qry, lfirst(l), forUpdate, noWait, pushedDown);
}
else if (IsA(jtnode, JoinExpr))
{
JoinExpr *j = (JoinExpr *) jtnode;
- markQueryForLocking(qry, j->larg, forUpdate, noWait);
- markQueryForLocking(qry, j->rarg, forUpdate, noWait);
+ markQueryForLocking(qry, j->larg, forUpdate, noWait, pushedDown);
+ markQueryForLocking(qry, j->rarg, forUpdate, noWait, pushedDown);
}
else
elog(ERROR, "unrecognized node type: %d",
/* Do what we came for */
sub->subselect = (Node *) fireRIRrules((Query *) sub->subselect,
- activeRIRs);
+ activeRIRs, false);
/* Fall through to process lefthand args of SubLink */
}
* 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
*/
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;
}
!rangeTableEntry_used((Node *) parsetree, rt_index, 0))
continue;
+ /*
+ * Also, if this is a new result relation introduced by
+ * ApplyRetrieveRule, we don't want to do anything more with it.
+ */
+ if (rt_index == parsetree->resultRelation &&
+ rt_index != origResultRelation)
+ continue;
+
/*
* We can use NoLock here since either the parser or
* AcquireRewriteLocks should have locked the rel already.
rt_index,
rule->attrno == -1,
rel,
- activeRIRs);
+ activeRIRs,
+ forUpdatePushedDown);
}
activeRIRs = list_delete_first(activeRIRs);
heap_close(rel, NoLock);
}
+ /* Recurse into subqueries in WITH */
+ foreach(lc, parsetree->cteList)
+ {
+ CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
+
+ cte->ctequery = (Node *)
+ fireRIRrules((Query *) cte->ctequery, activeRIRs, false);
+ }
+
/*
* Recurse into sublink subqueries, too. But we already did the ones in
- * the rtable.
+ * the rtable and cteList.
*/
if (parsetree->hasSubLinks)
query_tree_walker(parsetree, fireRIRonSubLink, (void *) activeRIRs,
- QTW_IGNORE_RT_SUBQUERIES);
+ QTW_IGNORE_RC_SUBQUERIES);
return parsetree;
}
rt_fetch(rt_index, parsetree->rtable),
parsetree->targetList,
event,
- rt_index);
+ rt_index,
+ &parsetree->hasSubLinks);
/* And attach the fixed qual */
AddInvertedQual(parsetree, new_qual);
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
rt_entry_relation = heap_open(rt_entry->relid, NoLock);
/*
- * If it's an INSERT or UPDATE, rewrite the targetlist into standard
- * form. This will be needed by the planner anyway, and doing it now
- * ensures that any references to NEW.field will behave sanely.
+ * Rewrite the targetlist as needed for the command type.
*/
- if (event == CMD_UPDATE)
- rewriteTargetList(parsetree, rt_entry_relation, NULL);
- else if (event == CMD_INSERT)
+ if (event == CMD_INSERT)
{
RangeTblEntry *values_rte = NULL;
List *attrnos;
/* Process the main targetlist ... */
- rewriteTargetList(parsetree, rt_entry_relation, &attrnos);
+ rewriteTargetListIU(parsetree, rt_entry_relation, &attrnos);
/* ... and the VALUES expression lists */
rewriteValuesRTE(values_rte, rt_entry_relation, attrnos);
}
else
{
/* Process just the main targetlist */
- rewriteTargetList(parsetree, rt_entry_relation, NULL);
+ rewriteTargetListIU(parsetree, rt_entry_relation, NULL);
}
}
+ else if (event == CMD_UPDATE)
+ {
+ rewriteTargetListIU(parsetree, rt_entry_relation, NULL);
+ rewriteTargetListUD(parsetree, rt_entry, rt_entry_relation);
+ }
+ else if (event == CMD_DELETE)
+ {
+ rewriteTargetListUD(parsetree, rt_entry, rt_entry_relation);
+ }
+ else
+ elog(ERROR, "unrecognized commandType: %d", (int) event);
/*
* Collect and apply the appropriate rules.
case CMD_INSERT:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot perform INSERT RETURNING on relation \"%s\"",
- RelationGetRelationName(rt_entry_relation)),
+ errmsg("cannot perform INSERT RETURNING on relation \"%s\"",
+ RelationGetRelationName(rt_entry_relation)),
errhint("You need an unconditional ON INSERT DO INSTEAD rule with a RETURNING clause.")));
break;
case CMD_UPDATE:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot perform UPDATE RETURNING on relation \"%s\"",
- RelationGetRelationName(rt_entry_relation)),
+ errmsg("cannot perform UPDATE RETURNING on relation \"%s\"",
+ RelationGetRelationName(rt_entry_relation)),
errhint("You need an unconditional ON UPDATE DO INSTEAD rule with a RETURNING clause.")));
break;
case CMD_DELETE:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot perform DELETE RETURNING on relation \"%s\"",
- RelationGetRelationName(rt_entry_relation)),
+ errmsg("cannot perform DELETE RETURNING on relation \"%s\"",
+ RelationGetRelationName(rt_entry_relation)),
errhint("You need an unconditional ON DELETE DO INSTEAD rule with a RETURNING clause.")));
break;
default:
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);
}