/*-------------------------------------------------------------------------
*
* rewriteHandler.c
+ * Primary module of query rewriter.
*
- * Copyright (c) 1994, Regents of the University of California
- *
+ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.37 1999/02/22 05:26:46 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.122 2003/07/03 16:34:25 tgl Exp $
*
*-------------------------------------------------------------------------
*/
-#include <string.h>
#include "postgres.h"
-#include "miscadmin.h"
-#include "utils/palloc.h"
-#include "utils/elog.h"
-#include "utils/rel.h"
-#include "nodes/pg_list.h"
-#include "nodes/primnodes.h"
-#include "nodes/relation.h"
-
-#include "parser/parsetree.h" /* for parsetree manipulation */
-#include "parser/parse_relation.h"
-#include "nodes/parsenodes.h"
-
-/***S*I***/
-#include "parser/parse_node.h"
-#include "parser/parse_target.h"
-#include "parser/analyze.h"
+#include "access/heapam.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_type.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "optimizer/clauses.h"
#include "optimizer/prep.h"
-
-#include "rewrite/rewriteSupport.h"
+#include "optimizer/var.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/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
-#include "rewrite/locks.h"
-
-#include "commands/creatinh.h"
-#include "access/heapam.h"
-
+#include "utils/builtins.h"
#include "utils/lsyscache.h"
-#include "utils/syscache.h"
-#include "utils/acl.h"
-#include "catalog/pg_shadow.h"
-#include "catalog/pg_type.h"
-static RewriteInfo *gatherRewriteMeta(Query *parsetree,
+/* We use a list of these to detect recursion in RewriteQuery */
+typedef struct rewrite_event {
+ Oid relation; /* OID of relation having rules */
+ CmdType event; /* type of rule being fired */
+} rewrite_event;
+
+static Query *rewriteRuleAction(Query *parsetree,
Query *rule_action,
Node *rule_qual,
int rt_index,
- CmdType event,
- bool *instead_flag);
-static bool rangeTableEntry_used(Node *node, int rt_index, int sublevels_up);
-static bool attribute_used(Node *node, int rt_index, int attno, int sublevels_up);
-static void modifyAggrefUplevel(Node *node);
-static void modifyAggrefChangeVarnodes(Node **nodePtr, int rt_index, int new_index, int sublevels_up);
-static void modifyAggrefDropQual(Node **nodePtr, Node *orignode, Expr *expr);
-static SubLink *modifyAggrefMakeSublink(Expr *origexp, Query *parsetree);
-static void modifyAggrefQual(Node **nodePtr, Query *parsetree);
-
-
-static Query *fireRIRrules(Query *parsetree);
+ CmdType event);
+static List *adjustJoinTreeList(Query *parsetree, bool removert, int rt_index);
+static void rewriteTargetList(Query *parsetree, Relation target_relation);
+static TargetEntry *process_matched_tle(TargetEntry *src_tle,
+ TargetEntry *prior_tle);
+static void markQueryForUpdate(Query *qry, bool skipOldNew);
+static List *matchLocks(CmdType event, RuleLock *rulelocks,
+ int varno, Query *parsetree);
+static Query *fireRIRrules(Query *parsetree, List *activeRIRs);
/*
- * gatherRewriteMeta -
- * Gather meta information about parsetree, and rule. Fix rule body
- * and qualifier so that they can be mixed with the parsetree and
- * maintain semantic validity
+ * rewriteRuleAction -
+ * Rewrite the rule action with appropriate qualifiers (taken from
+ * the triggering query).
*/
-static RewriteInfo *
-gatherRewriteMeta(Query *parsetree,
+static Query *
+rewriteRuleAction(Query *parsetree,
Query *rule_action,
Node *rule_qual,
int rt_index,
- CmdType event,
- bool *instead_flag)
+ CmdType event)
{
- RewriteInfo *info;
+ int current_varno,
+ new_varno;
+ List *main_rtable;
int rt_length;
- int result_reln;
-
- info = (RewriteInfo *) palloc(sizeof(RewriteInfo));
- info->rt_index = rt_index;
- info->event = event;
- info->instead_flag = *instead_flag;
- info->rule_action = (Query *) copyObject(rule_action);
- info->rule_qual = (Node *) copyObject(rule_qual);
- if (info->rule_action == NULL)
- info->nothing = TRUE;
- else
+ Query *sub_action;
+ Query **sub_action_ptr;
+ List *rt;
+
+ /*
+ * 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);
+
+ current_varno = rt_index;
+ rt_length = 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.
+ *
+ * If the rule action is an INSERT...SELECT, the OLD/NEW rtable entries
+ * will be in the SELECT part, and we have to modify that rather than
+ * the top-level INSERT (kluge!).
+ */
+ sub_action = getInsertSelectQuery(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 */
+ ChangeVarNodes((Node *) sub_action,
+ PRS2_OLD_VARNO + rt_length, rt_index, 0);
+ ChangeVarNodes(rule_qual,
+ PRS2_OLD_VARNO + rt_length, rt_index, 0);
+
+ /*
+ * 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 if we tried to remove them we'd have a much
+ * harder job to adjust RT indexes in the query's Vars. It's OK to
+ * have unused RT entries, since planner will ignore them.
+ *
+ * NOTE: 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.
+ *
+ * Also, we must disable write-access checking in all the RT entries
+ * copied from the main query. This is safe since in fact the rule action
+ * won't write on them, and it's necessary because the rule action may
+ * have a different commandType than the main query, causing
+ * ExecCheckRTEPerms() to make an inappropriate check. The read-access
+ * checks can be left enabled, although they're probably redundant.
+ */
+ main_rtable = (List *) copyObject(parsetree->rtable);
+
+ foreach(rt, main_rtable)
{
- info->nothing = FALSE;
- info->action = info->rule_action->commandType;
- info->current_varno = rt_index;
- info->rt = parsetree->rtable;
- rt_length = length(info->rt);
- info->rt = nconc(info->rt, copyObject(info->rule_action->rtable));
-
- info->new_varno = PRS2_NEW_VARNO + rt_length;
- OffsetVarNodes(info->rule_action->qual, rt_length, 0);
- OffsetVarNodes((Node *) info->rule_action->targetList, rt_length, 0);
- OffsetVarNodes(info->rule_qual, rt_length, 0);
- ChangeVarNodes((Node *) info->rule_action->qual,
- PRS2_CURRENT_VARNO + rt_length, rt_index, 0);
- ChangeVarNodes((Node *) info->rule_action->targetList,
- PRS2_CURRENT_VARNO + rt_length, rt_index, 0);
- ChangeVarNodes(info->rule_qual,
- PRS2_CURRENT_VARNO + rt_length, rt_index, 0);
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
- /*
- * bug here about replace CURRENT -- sort of replace current is
- * deprecated now so this code shouldn't really need to be so
- * clutzy but.....
- */
- if (info->action != CMD_SELECT)
- { /* i.e update XXXXX */
- int new_result_reln = 0;
+ rte->checkForWrite = false;
+ }
- result_reln = info->rule_action->resultRelation;
- switch (result_reln)
- {
- case PRS2_CURRENT_VARNO:
- new_result_reln = rt_index;
- break;
- case PRS2_NEW_VARNO: /* XXX */
- default:
- new_result_reln = result_reln + rt_length;
- break;
- }
- info->rule_action->resultRelation = new_result_reln;
- }
+ sub_action->rtable = nconc(main_rtable, sub_action->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.
+ *
+ * As above, the action's jointree must not share substructure with the
+ * main parsetree's.
+ */
+ if (sub_action->jointree != NULL)
+ {
+ bool keeporig;
+ List *newjointree;
+
+ 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));
+ newjointree = adjustJoinTreeList(parsetree, !keeporig, rt_index);
+ sub_action->jointree->fromlist =
+ nconc(newjointree, sub_action->jointree->fromlist);
}
- return info;
-}
+ /*
+ * 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
+ */
+ AddQual(sub_action, rule_qual);
+
+ AddQual(sub_action, parsetree->jointree->quals);
+
+ /*
+ * Rewrite new.attribute w/ right hand side of target-list entry for
+ * appropriate field name in insert/update.
+ *
+ * KLUGE ALERT: since ResolveNew returns a mutated copy, we can't just
+ * apply it to sub_action; we have to remember to update the sublink
+ * inside rule_action, too.
+ */
+ if (event == CMD_INSERT || event == CMD_UPDATE)
+ {
+ sub_action = (Query *) ResolveNew((Node *) sub_action,
+ new_varno,
+ 0,
+ parsetree->targetList,
+ event,
+ current_varno);
+ if (sub_action_ptr)
+ *sub_action_ptr = sub_action;
+ else
+ rule_action = sub_action;
+ }
+
+ return rule_action;
+}
/*
- * rangeTableEntry_used -
- * we need to process a RTE for RIR rules only if it is
- * referenced somewhere in var nodes of the query.
+ * Copy the query's jointree list, and optionally attempt to remove any
+ * occurrence of the given rt_index as a top-level join item (we do not look
+ * for it within join items; this is OK because we are only expecting to find
+ * it as an UPDATE or DELETE target relation, which will be at the top level
+ * of the join). Returns modified jointree list --- this is a separate copy
+ * sharing no nodes with the original.
*/
-static bool
-rangeTableEntry_used(Node *node, int rt_index, int sublevels_up)
+static List *
+adjustJoinTreeList(Query *parsetree, bool removert, int rt_index)
{
- if (node == NULL)
- return FALSE;
-
- switch(nodeTag(node)) {
- case T_TargetEntry:
- {
- TargetEntry *tle = (TargetEntry *)node;
+ List *newjointree = copyObject(parsetree->jointree->fromlist);
+ List *jjt;
- return rangeTableEntry_used(
- (Node *)(tle->expr),
- rt_index,
- sublevels_up);
- }
- break;
+ if (removert)
+ {
+ foreach(jjt, newjointree)
+ {
+ RangeTblRef *rtr = lfirst(jjt);
- case T_Aggref:
+ if (IsA(rtr, RangeTblRef) &&
+ rtr->rtindex == rt_index)
{
- Aggref *aggref = (Aggref *)node;
-
- return rangeTableEntry_used(
- (Node *)(aggref->target),
- rt_index,
- sublevels_up);
+ newjointree = lremove(rtr, newjointree);
+ /* foreach is safe because we exit loop after lremove... */
+ break;
}
- break;
+ }
+ }
+ return newjointree;
+}
- case T_GroupClause:
- {
- GroupClause *grp = (GroupClause *)node;
- return rangeTableEntry_used(
- (Node *)(grp->entry),
- rt_index,
- sublevels_up);
- }
- break;
+/*
+ * rewriteTargetList - rewrite INSERT/UPDATE targetlist into standard form
+ *
+ * This has the following responsibilities:
+ *
+ * 1. For an INSERT, add tlist entries to compute default values for any
+ * attributes that have defaults and are not assigned to in the given tlist.
+ * (We do not insert anything for default-less attributes, however. The
+ * planner will later insert NULLs for them, but there's no reason to slow
+ * down rewriter processing with extra tlist nodes.) Also, for both INSERT
+ * 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".
+ * 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,
+ * 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
+ * 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.
+ */
+static void
+rewriteTargetList(Query *parsetree, Relation target_relation)
+{
+ CmdType commandType = parsetree->commandType;
+ List *tlist = parsetree->targetList;
+ List *new_tlist = NIL;
+ int attrno,
+ numattrs;
+ List *temp;
- case T_Expr:
- {
- Expr *exp = (Expr *)node;
+ /*
+ * Scan the tuple description in the relation's relcache entry to make
+ * sure we have all the user attributes in the right order.
+ */
+ numattrs = RelationGetNumberOfAttributes(target_relation);
- return rangeTableEntry_used(
- (Node *)(exp->args),
- rt_index,
- sublevels_up);
- }
- break;
+ for (attrno = 1; attrno <= numattrs; attrno++)
+ {
+ Form_pg_attribute att_tup = target_relation->rd_att->attrs[attrno - 1];
+ TargetEntry *new_tle = NULL;
- case T_Iter:
- {
- Iter *iter = (Iter *)node;
+ /* We can ignore deleted attributes */
+ if (att_tup->attisdropped)
+ continue;
- return rangeTableEntry_used(
- (Node *)(iter->iterexpr),
- rt_index,
- sublevels_up);
- }
- break;
+ /*
+ * Look for targetlist entries matching this attr. We match by
+ * resno, but the resname should match too.
+ *
+ * Junk attributes are not candidates to be matched.
+ */
+ foreach(temp, tlist)
+ {
+ TargetEntry *old_tle = (TargetEntry *) lfirst(temp);
+ Resdom *resdom = old_tle->resdom;
- case T_ArrayRef:
+ if (!resdom->resjunk && resdom->resno == attrno)
{
- ArrayRef *ref = (ArrayRef *)node;
-
- if (rangeTableEntry_used(
- (Node *)(ref->refupperindexpr),
- rt_index,
- sublevels_up))
- return TRUE;
-
- if (rangeTableEntry_used(
- (Node *)(ref->reflowerindexpr),
- rt_index,
- sublevels_up))
- return TRUE;
-
- if (rangeTableEntry_used(
- (Node *)(ref->refexpr),
- rt_index,
- sublevels_up))
- return TRUE;
-
- if (rangeTableEntry_used(
- (Node *)(ref->refassgnexpr),
- rt_index,
- sublevels_up))
- return TRUE;
-
- return FALSE;
+ Assert(strcmp(resdom->resname,
+ NameStr(att_tup->attname)) == 0);
+ new_tle = process_matched_tle(old_tle, new_tle);
+ /* keep scanning to detect multiple assignments to attr */
}
- break;
+ }
- case T_Var:
- {
- Var *var = (Var *)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)))
+ {
+ Node *new_expr;
+
+ new_expr = build_column_default(target_relation, attrno);
- if (var->varlevelsup == sublevels_up)
- return var->varno == rt_index;
+ /*
+ * 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)
+ {
+ if (commandType == CMD_INSERT)
+ new_tle = NULL;
else
- return FALSE;
+ {
+ new_expr = (Node *) makeConst(att_tup->atttypid,
+ 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,
+ att_tup->atttypid,
+ COERCE_IMPLICIT_CAST);
+ }
}
- break;
- case T_Param:
- return FALSE;
+ if (new_expr)
+ new_tle = makeTargetEntry(makeResdom(attrno,
+ att_tup->atttypid,
+ att_tup->atttypmod,
+ pstrdup(NameStr(att_tup->attname)),
+ false),
+ (Expr *) new_expr);
+ }
- case T_Const:
- return FALSE;
+ if (new_tle)
+ new_tlist = lappend(new_tlist, new_tle);
+ }
- case T_List:
- {
- List *l;
-
- foreach (l, (List *)node) {
- if (rangeTableEntry_used(
- (Node *)lfirst(l),
- rt_index,
- sublevels_up))
- return TRUE;
- }
- return FALSE;
- }
- break;
+ /*
+ * 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;
- case T_SubLink:
+ if (resdom->resjunk)
+ {
+ /* Get the resno right, but don't copy unnecessarily */
+ if (resdom->resno != attrno)
{
- SubLink *sub = (SubLink *)node;
+ 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, "rewriteTargetList: bogus resno %d in targetlist",
+ resdom->resno);
+ }
+ }
- if (rangeTableEntry_used(
- (Node *)(sub->lefthand),
- rt_index,
- sublevels_up))
- return TRUE;
+ parsetree->targetList = new_tlist;
+}
- if (rangeTableEntry_used(
- (Node *)(sub->subselect),
- rt_index,
- sublevels_up + 1))
- return TRUE;
- return FALSE;
- }
- break;
+/*
+ * Convert a matched TLE from the original tlist into a correct new TLE.
+ *
+ * This routine detects and handles multiple assignments to the same target
+ * attribute.
+ */
+static TargetEntry *
+process_matched_tle(TargetEntry *src_tle,
+ TargetEntry *prior_tle)
+{
+ Resdom *resdom = src_tle->resdom;
+ Node *priorbottom;
+ ArrayRef *newexpr;
- case T_CaseExpr:
- {
- CaseExpr *exp = (CaseExpr *)node;
+ if (prior_tle == NULL)
+ {
+ /*
+ * Normal case where this is the first assignment to the
+ * attribute.
+ */
+ return src_tle;
+ }
- if (rangeTableEntry_used(
- (Node *)(exp->args),
- rt_index,
- sublevels_up))
- return TRUE;
+ /*
+ * Multiple assignments to same attribute. Allow only if all are
+ * array-assign operators with same bottom array object.
+ */
+ 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)
+ elog(ERROR, "Multiple assignments to same attribute \"%s\"",
+ resdom->resname);
- if (rangeTableEntry_used(
- (Node *)(exp->defresult),
- rt_index,
- sublevels_up))
- return TRUE;
+ /*
+ * Prior TLE could be a nest of ArrayRefs 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))
+ elog(ERROR, "Multiple assignments to same attribute \"%s\"",
+ resdom->resname);
- return FALSE;
- }
- break;
+ /*
+ * Looks OK to nest 'em.
+ */
+ newexpr = makeNode(ArrayRef);
+ memcpy(newexpr, src_tle->expr, sizeof(ArrayRef));
+ newexpr->refexpr = prior_tle->expr;
- case T_CaseWhen:
- {
- CaseWhen *when = (CaseWhen *)node;
+ return makeTargetEntry(resdom, (Expr *) newexpr);
+}
- if (rangeTableEntry_used(
- (Node *)(when->expr),
- rt_index,
- sublevels_up))
- return TRUE;
- if (rangeTableEntry_used(
- (Node *)(when->result),
- rt_index,
- sublevels_up))
- return TRUE;
+/*
+ * Make an expression tree for the default value for a column.
+ *
+ * If there is no default, return a NULL instead.
+ */
+Node *
+build_column_default(Relation rel, int attrno)
+{
+ TupleDesc rd_att = rel->rd_att;
+ Form_pg_attribute att_tup = rd_att->attrs[attrno - 1];
+ Oid atttype = att_tup->atttypid;
+ int32 atttypmod = att_tup->atttypmod;
+ Node *expr = NULL;
+ Oid exprtype;
- return FALSE;
- }
- break;
+ /*
+ * Scan to see if relation has a default for this column.
+ */
+ if (rd_att->constr && rd_att->constr->num_defval > 0)
+ {
+ AttrDefault *defval = rd_att->constr->defval;
+ int ndef = rd_att->constr->num_defval;
- case T_Query:
+ while (--ndef >= 0)
+ {
+ if (attrno == defval[ndef].adnum)
{
- Query *qry = (Query *)node;
-
- if (rangeTableEntry_used(
- (Node *)(qry->targetList),
- rt_index,
- sublevels_up))
- return TRUE;
-
- if (rangeTableEntry_used(
- (Node *)(qry->qual),
- rt_index,
- sublevels_up))
- return TRUE;
-
- if (rangeTableEntry_used(
- (Node *)(qry->havingQual),
- rt_index,
- sublevels_up))
- return TRUE;
-
- if (rangeTableEntry_used(
- (Node *)(qry->groupClause),
- rt_index,
- sublevels_up))
- return TRUE;
-
- return FALSE;
+ /*
+ * Found it, convert string representation to node tree.
+ */
+ expr = stringToNode(defval[ndef].adbin);
+ break;
}
- break;
+ }
+ }
- default:
- elog(NOTICE, "unknown node tag %d in rangeTableEntry_used()", nodeTag(node));
- elog(NOTICE, "Node is: %s", nodeToString(node));
- break;
+ if (expr == NULL)
+ {
+ /*
+ * No per-column default, so look for a default for the type
+ * itself.
+ */
+ if (att_tup->attisset)
+ {
+ /*
+ * Set attributes are represented as OIDs no matter what the
+ * set element type is, and the element type's default is
+ * irrelevant too.
+ */
+ }
+ else
+ expr = get_typdefault(atttype);
+ }
+ if (expr == NULL)
+ return NULL; /* No default anywhere */
- }
+ /*
+ * Make sure the value is coerced to the target column type (might not
+ * be right type yet if it's not a constant!) This should match the
+ * parser's processing of non-defaulted expressions --- see
+ * updateTargetListEntry().
+ */
+ exprtype = exprType(expr);
- return FALSE;
+ expr = coerce_to_target_type(NULL, /* no UNKNOWN params here */
+ expr, exprtype,
+ atttype, atttypmod,
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST);
+ /*
+ * This really shouldn't fail; should have checked the default's
+ * type when it was created ...
+ */
+ if (expr == NULL)
+ elog(ERROR, "Column \"%s\" is of type %s"
+ " but default expression is of type %s"
+ "\n\tYou will need to rewrite or cast the expression",
+ NameStr(att_tup->attname),
+ format_type_be(atttype),
+ format_type_be(exprtype));
+
+ return expr;
}
/*
- * attribute_used -
- * Check if a specific attribute number of a RTE is used
- * somewhere in the query
+ * matchLocks -
+ * match the list of locks and returns the matching rules
*/
-static bool
-attribute_used(Node *node, int rt_index, int attno, int sublevels_up)
+static List *
+matchLocks(CmdType event,
+ RuleLock *rulelocks,
+ int varno,
+ Query *parsetree)
{
- if (node == NULL)
- return FALSE;
-
- switch(nodeTag(node)) {
- case T_TargetEntry:
- {
- TargetEntry *tle = (TargetEntry *)node;
-
- return attribute_used(
- (Node *)(tle->expr),
- rt_index,
- attno,
- sublevels_up);
- }
- break;
-
- case T_Aggref:
- {
- Aggref *aggref = (Aggref *)node;
-
- return attribute_used(
- (Node *)(aggref->target),
- rt_index,
- attno,
- sublevels_up);
- }
- break;
+ List *matching_locks = NIL;
+ int nlocks;
+ int i;
- case T_GroupClause:
- {
- GroupClause *grp = (GroupClause *)node;
+ if (rulelocks == NULL)
+ return NIL;
- return attribute_used(
- (Node *)(grp->entry),
- rt_index,
- attno,
- sublevels_up);
- }
- break;
+ if (parsetree->commandType != CMD_SELECT)
+ {
+ if (parsetree->resultRelation != varno)
+ return NIL;
+ }
- case T_Expr:
- {
- Expr *exp = (Expr *)node;
+ nlocks = rulelocks->numLocks;
- return attribute_used(
- (Node *)(exp->args),
- rt_index,
- attno,
- sublevels_up);
- }
- break;
+ for (i = 0; i < nlocks; i++)
+ {
+ RewriteRule *oneLock = rulelocks->rules[i];
- case T_Iter:
- {
- Iter *iter = (Iter *)node;
+ if (oneLock->event == event)
+ {
+ if (parsetree->commandType != CMD_SELECT ||
+ (oneLock->attrno == -1 ?
+ rangeTableEntry_used((Node *) parsetree, varno, 0) :
+ attribute_used((Node *) parsetree,
+ varno, oneLock->attrno, 0)))
+ matching_locks = lappend(matching_locks, oneLock);
+ }
+ }
- return attribute_used(
- (Node *)(iter->iterexpr),
- rt_index,
- attno,
- sublevels_up);
- }
- break;
+ return matching_locks;
+}
- case T_ArrayRef:
- {
- ArrayRef *ref = (ArrayRef *)node;
-
- if (attribute_used(
- (Node *)(ref->refupperindexpr),
- rt_index,
- attno,
- sublevels_up))
- return TRUE;
-
- if (attribute_used(
- (Node *)(ref->reflowerindexpr),
- rt_index,
- attno,
- sublevels_up))
- return TRUE;
-
- if (attribute_used(
- (Node *)(ref->refexpr),
- rt_index,
- attno,
- sublevels_up))
- return TRUE;
-
- if (attribute_used(
- (Node *)(ref->refassgnexpr),
- rt_index,
- attno,
- sublevels_up))
- return TRUE;
-
- return FALSE;
- }
- break;
- case T_Var:
- {
- Var *var = (Var *)node;
+static Query *
+ApplyRetrieveRule(Query *parsetree,
+ RewriteRule *rule,
+ int rt_index,
+ bool relation_level,
+ Relation relation,
+ bool relIsUsed,
+ List *activeRIRs)
+{
+ Query *rule_action;
+ RangeTblEntry *rte,
+ *subrte;
- if (var->varlevelsup == sublevels_up)
- return var->varno == rt_index;
- else
- return FALSE;
- }
- break;
+ if (length(rule->actions) != 1)
+ elog(ERROR, "ApplyRetrieveRule: expected just one rule action");
+ if (rule->qual != NULL)
+ elog(ERROR, "ApplyRetrieveRule: can't handle qualified ON SELECT rule");
+ if (!relation_level)
+ elog(ERROR, "ApplyRetrieveRule: can't handle per-attribute ON SELECT rule");
- case T_Param:
- return FALSE;
+ /*
+ * Make a modifiable copy of the view query, and recursively expand
+ * any view references inside it.
+ */
+ rule_action = copyObject(lfirst(rule->actions));
- case T_Const:
- return FALSE;
+ rule_action = fireRIRrules(rule_action, activeRIRs);
- case T_List:
- {
- List *l;
-
- foreach (l, (List *)node) {
- if (attribute_used(
- (Node *)lfirst(l),
- rt_index,
- attno,
- sublevels_up))
- return TRUE;
- }
- return FALSE;
- }
- break;
+ /*
+ * VIEWs are really easy --- just plug the view query in as a
+ * subselect, replacing the relation's original RTE.
+ */
+ rte = rt_fetch(rt_index, parsetree->rtable);
- case T_SubLink:
- {
- SubLink *sub = (SubLink *)node;
-
- if (attribute_used(
- (Node *)(sub->lefthand),
- rt_index,
- attno,
- sublevels_up))
- return TRUE;
-
- if (attribute_used(
- (Node *)(sub->subselect),
- rt_index,
- attno,
- sublevels_up + 1))
- return TRUE;
-
- return FALSE;
- }
- break;
+ rte->rtekind = RTE_SUBQUERY;
+ rte->relid = InvalidOid;
+ rte->subquery = rule_action;
+ rte->inh = false; /* must not be set for a subquery */
- case T_Query:
- {
- Query *qry = (Query *)node;
-
- if (attribute_used(
- (Node *)(qry->targetList),
- rt_index,
- attno,
- sublevels_up))
- return TRUE;
-
- if (attribute_used(
- (Node *)(qry->qual),
- rt_index,
- attno,
- sublevels_up))
- return TRUE;
-
- if (attribute_used(
- (Node *)(qry->havingQual),
- rt_index,
- attno,
- sublevels_up))
- return TRUE;
-
- if (attribute_used(
- (Node *)(qry->groupClause),
- rt_index,
- attno,
- sublevels_up))
- return TRUE;
-
- return FALSE;
- }
- break;
+ /*
+ * 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->checkForRead = rte->checkForRead;
+ subrte->checkForWrite = rte->checkForWrite;
+ subrte->checkAsUser = rte->checkAsUser;
- default:
- elog(NOTICE, "unknown node tag %d in attribute_used()", nodeTag(node));
- elog(NOTICE, "Node is: %s", nodeToString(node));
- break;
+ rte->checkForRead = false; /* no permission check on subquery itself */
+ rte->checkForWrite = false;
+ rte->checkAsUser = InvalidOid;
+ /*
+ * FOR UPDATE of view?
+ */
+ 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);
}
- return FALSE;
+ return parsetree;
}
-
/*
- * modifyAggrefUplevel -
- * In the newly created sublink for an aggregate column used in
- * the qualification, we must adjust the varlevelsup in all the
- * var nodes.
+ * Recursively mark all relations used by a view as FOR UPDATE.
+ *
+ * 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.
*/
static void
-modifyAggrefUplevel(Node *node)
+markQueryForUpdate(Query *qry, bool skipOldNew)
{
- if (node == NULL)
- return;
+ Index rti = 0;
+ List *l;
- switch(nodeTag(node)) {
- case T_TargetEntry:
- {
- TargetEntry *tle = (TargetEntry *)node;
+ foreach(l, qry->rtable)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
- modifyAggrefUplevel(
- (Node *)(tle->expr));
- }
- break;
-
- case T_Aggref:
- {
- Aggref *aggref = (Aggref *)node;
-
- modifyAggrefUplevel(
- (Node *)(aggref->target));
- }
- break;
-
- case T_Expr:
- {
- Expr *exp = (Expr *)node;
-
- modifyAggrefUplevel(
- (Node *)(exp->args));
- }
- break;
-
- case T_Iter:
- {
- Iter *iter = (Iter *)node;
-
- modifyAggrefUplevel(
- (Node *)(iter->iterexpr));
- }
- break;
-
- case T_ArrayRef:
- {
- ArrayRef *ref = (ArrayRef *)node;
-
- modifyAggrefUplevel(
- (Node *)(ref->refupperindexpr));
- modifyAggrefUplevel(
- (Node *)(ref->reflowerindexpr));
- modifyAggrefUplevel(
- (Node *)(ref->refexpr));
- modifyAggrefUplevel(
- (Node *)(ref->refassgnexpr));
- }
- break;
-
- case T_Var:
- {
- Var *var = (Var *)node;
-
- var->varlevelsup++;
- }
- break;
-
- case T_Param:
- break;
-
- case T_Const:
- break;
-
- case T_List:
- {
- List *l;
-
- foreach (l, (List *)node)
- modifyAggrefUplevel(
- (Node *)lfirst(l));
- }
- break;
-
- case T_SubLink:
- {
- SubLink *sub = (SubLink *)node;
-
- modifyAggrefUplevel(
- (Node *)(sub->lefthand));
-
- modifyAggrefUplevel(
- (Node *)(sub->oper));
-
- modifyAggrefUplevel(
- (Node *)(sub->subselect));
- }
- break;
-
- case T_Query:
- {
- Query *qry = (Query *)node;
-
- modifyAggrefUplevel(
- (Node *)(qry->targetList));
-
- modifyAggrefUplevel(
- (Node *)(qry->qual));
-
- modifyAggrefUplevel(
- (Node *)(qry->havingQual));
-
- modifyAggrefUplevel(
- (Node *)(qry->groupClause));
- }
- break;
-
- default:
- elog(NOTICE, "unknown node tag %d in modifyAggrefUplevel()", nodeTag(node));
- elog(NOTICE, "Node is: %s", nodeToString(node));
- break;
-
-
- }
-}
-
-
-/*
- * modifyAggrefChangeVarnodes -
- * Change the var nodes in a sublink created for an aggregate column
- * used in the qualification that is subject of the aggregate
- * function to point to the correct local RTE.
- */
-static void
-modifyAggrefChangeVarnodes(Node **nodePtr, int rt_index, int new_index, int sublevels_up)
-{
- Node *node = *nodePtr;
-
- if (node == NULL)
- return;
-
- switch(nodeTag(node)) {
- case T_TargetEntry:
- {
- TargetEntry *tle = (TargetEntry *)node;
-
- modifyAggrefChangeVarnodes(
- (Node **)(&(tle->expr)),
- rt_index,
- new_index,
- sublevels_up);
- }
- break;
-
- case T_Aggref:
- {
- Aggref *aggref = (Aggref *)node;
-
- modifyAggrefChangeVarnodes(
- (Node **)(&(aggref->target)),
- rt_index,
- new_index,
- sublevels_up);
- }
- break;
-
- case T_GroupClause:
- {
- GroupClause *grp = (GroupClause *)node;
-
- modifyAggrefChangeVarnodes(
- (Node **)(&(grp->entry)),
- rt_index,
- new_index,
- sublevels_up);
- }
- break;
-
- case T_Expr:
- {
- Expr *exp = (Expr *)node;
-
- modifyAggrefChangeVarnodes(
- (Node **)(&(exp->args)),
- rt_index,
- new_index,
- sublevels_up);
- }
- break;
-
- case T_Iter:
- {
- Iter *iter = (Iter *)node;
-
- modifyAggrefChangeVarnodes(
- (Node **)(&(iter->iterexpr)),
- rt_index,
- new_index,
- sublevels_up);
- }
- break;
-
- case T_ArrayRef:
- {
- ArrayRef *ref = (ArrayRef *)node;
-
- modifyAggrefChangeVarnodes(
- (Node **)(&(ref->refupperindexpr)),
- rt_index,
- new_index,
- sublevels_up);
- modifyAggrefChangeVarnodes(
- (Node **)(&(ref->reflowerindexpr)),
- rt_index,
- new_index,
- sublevels_up);
- modifyAggrefChangeVarnodes(
- (Node **)(&(ref->refexpr)),
- rt_index,
- new_index,
- sublevels_up);
- modifyAggrefChangeVarnodes(
- (Node **)(&(ref->refassgnexpr)),
- rt_index,
- new_index,
- sublevels_up);
- }
- break;
-
- case T_Var:
- {
- Var *var = (Var *)node;
-
- if (var->varlevelsup == sublevels_up &&
- var->varno == rt_index) {
- var = copyObject(var);
- var->varno = new_index;
- var->varnoold = new_index;
- var->varlevelsup = 0;
-
- *nodePtr = (Node *)var;
- }
- }
- break;
-
- case T_Param:
- break;
-
- case T_Const:
- break;
-
- case T_List:
- {
- List *l;
-
- foreach (l, (List *)node)
- modifyAggrefChangeVarnodes(
- (Node **)(&lfirst(l)),
- rt_index,
- new_index,
- sublevels_up);
- }
- break;
-
- case T_SubLink:
- {
- SubLink *sub = (SubLink *)node;
-
- modifyAggrefChangeVarnodes(
- (Node **)(&(sub->lefthand)),
- rt_index,
- new_index,
- sublevels_up);
-
- modifyAggrefChangeVarnodes(
- (Node **)(&(sub->oper)),
- rt_index,
- new_index,
- sublevels_up);
-
- modifyAggrefChangeVarnodes(
- (Node **)(&(sub->subselect)),
- rt_index,
- new_index,
- sublevels_up + 1);
- }
- break;
-
- case T_Query:
- {
- Query *qry = (Query *)node;
-
- modifyAggrefChangeVarnodes(
- (Node **)(&(qry->targetList)),
- rt_index,
- new_index,
- sublevels_up);
-
- modifyAggrefChangeVarnodes(
- (Node **)(&(qry->qual)),
- rt_index,
- new_index,
- sublevels_up);
-
- modifyAggrefChangeVarnodes(
- (Node **)(&(qry->havingQual)),
- rt_index,
- new_index,
- sublevels_up);
-
- modifyAggrefChangeVarnodes(
- (Node **)(&(qry->groupClause)),
- rt_index,
- new_index,
- sublevels_up);
- }
- break;
-
- default:
- elog(NOTICE, "unknown node tag %d in modifyAggrefChangeVarnodes()", nodeTag(node));
- elog(NOTICE, "Node is: %s", nodeToString(node));
- break;
+ 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;
+ if (rte->rtekind == RTE_RELATION)
+ {
+ if (!intMember(rti, qry->rowMarks))
+ qry->rowMarks = lappendi(qry->rowMarks, rti);
+ rte->checkForWrite = true;
+ }
+ else if (rte->rtekind == RTE_SUBQUERY)
+ {
+ /* FOR UPDATE of subquery is propagated to subquery's rels */
+ markQueryForUpdate(rte->subquery, false);
+ }
}
}
/*
- * modifyAggrefDropQual -
- * remove the pure aggref clase from a qualification
+ * fireRIRonSubLink -
+ * Apply fireRIRrules() to each SubLink (subselect in expression) found
+ * in the given tree.
+ *
+ * NOTE: although this has the form of a walker, we cheat and modify the
+ * SubLink nodes in-place. It is caller's responsibility to ensure that
+ * no unwanted side-effects occur!
+ *
+ * This is unlike most of the other routines that recurse into subselects,
+ * because we must take control at the SubLink node in order to replace
+ * the SubLink's subselect link with the possibly-rewritten subquery.
*/
-static void
-modifyAggrefDropQual(Node **nodePtr, Node *orignode, Expr *expr)
+static bool
+fireRIRonSubLink(Node *node, List *activeRIRs)
{
- Node *node = *nodePtr;
-
if (node == NULL)
- return;
-
- switch(nodeTag(node)) {
- case T_Var:
- break;
-
- case T_Aggref:
- {
- Aggref *aggref = (Aggref *)node;
- Aggref *oaggref = (Aggref *)orignode;
-
- modifyAggrefDropQual(
- (Node **)(&(aggref->target)),
- (Node *)(oaggref->target),
- expr);
- }
- break;
-
- case T_Param:
- break;
-
- case T_Const:
- break;
-
- case T_GroupClause:
- break;
-
- case T_Expr:
- {
- Expr *this_expr = (Expr *)node;
- Expr *orig_expr = (Expr *)orignode;
-
- if (orig_expr == expr) {
- Const *ctrue;
-
- if (expr->typeOid != BOOLOID)
- elog(ERROR,
- "aggregate expression in qualification isn't of type bool");
- ctrue = makeNode(Const);
- ctrue->consttype = BOOLOID;
- ctrue->constlen = 1;
- ctrue->constisnull = FALSE;
- ctrue->constvalue = (Datum)TRUE;
- ctrue->constbyval = TRUE;
-
- *nodePtr = (Node *)ctrue;
- }
- else
- modifyAggrefDropQual(
- (Node **)(&(this_expr->args)),
- (Node *)(orig_expr->args),
- expr);
- }
- break;
-
- case T_Iter:
- {
- Iter *iter = (Iter *)node;
- Iter *oiter = (Iter *)orignode;
-
- modifyAggrefDropQual(
- (Node **)(&(iter->iterexpr)),
- (Node *)(oiter->iterexpr),
- expr);
- }
- break;
-
- case T_ArrayRef:
- {
- ArrayRef *ref = (ArrayRef *)node;
- ArrayRef *oref = (ArrayRef *)orignode;
-
- modifyAggrefDropQual(
- (Node **)(&(ref->refupperindexpr)),
- (Node *)(oref->refupperindexpr),
- expr);
- modifyAggrefDropQual(
- (Node **)(&(ref->reflowerindexpr)),
- (Node *)(oref->reflowerindexpr),
- expr);
- modifyAggrefDropQual(
- (Node **)(&(ref->refexpr)),
- (Node *)(oref->refexpr),
- expr);
- modifyAggrefDropQual(
- (Node **)(&(ref->refassgnexpr)),
- (Node *)(oref->refassgnexpr),
- expr);
- }
- break;
-
- case T_List:
- {
- List *l;
- List *ol = (List *)orignode;
- int li = 0;
-
- foreach (l, (List *)node) {
- modifyAggrefDropQual(
- (Node **)(&(lfirst(l))),
- (Node *)nth(li, ol),
- expr);
- li++;
- }
- }
- break;
-
- case T_SubLink:
- {
- SubLink *sub = (SubLink *)node;
- SubLink *osub = (SubLink *)orignode;
-
- modifyAggrefDropQual(
- (Node **)(&(sub->subselect)),
- (Node *)(osub->subselect),
- expr);
- }
- break;
-
- case T_Query:
- {
- Query *qry = (Query *)node;
- Query *oqry = (Query *)orignode;
-
- modifyAggrefDropQual(
- (Node **)(&(qry->qual)),
- (Node *)(oqry->qual),
- expr);
-
- modifyAggrefDropQual(
- (Node **)(&(qry->havingQual)),
- (Node *)(oqry->havingQual),
- expr);
- }
- break;
-
- default:
- elog(NOTICE, "unknown node tag %d in modifyAggrefDropQual()", nodeTag(node));
- elog(NOTICE, "Node is: %s", nodeToString(node));
- break;
-
-
- }
-}
-
-
-/*
- * modifyAggrefMakeSublink -
- * Create a sublink node for a qualification expression that
- * uses an aggregate column of a view
- */
-static SubLink *
-modifyAggrefMakeSublink(Expr *origexp, Query *parsetree)
-{
- SubLink *sublink;
- Query *subquery;
- Node *subqual;
- RangeTblEntry *rte;
- Aggref *aggref;
- Var *target;
- TargetEntry *tle;
- Resdom *resdom;
- Expr *exp = copyObject(origexp);
-
- if (nodeTag(nth(0, exp->args)) == T_Aggref)
+ return false;
+ if (IsA(node, SubLink))
{
- if (nodeTag(nth(1, exp->args)) == T_Aggref)
- elog(ERROR, "rewrite: comparision of 2 aggregate columns not supported");
- else
- elog(ERROR, "rewrite: aggregate column of view must be at rigth side in qual");
+ SubLink *sub = (SubLink *) node;
+
+ /* Do what we came for */
+ sub->subselect = (Node *) fireRIRrules((Query *) sub->subselect,
+ activeRIRs);
+ /* Fall through to process lefthand args of SubLink */
}
- aggref = (Aggref *)nth(1, exp->args);
- target = (Var *)(aggref->target);
- rte = (RangeTblEntry *)nth(target->varno - 1, parsetree->rtable);
- tle = makeNode(TargetEntry);
- resdom = makeNode(Resdom);
-
- aggref->usenulls = TRUE;
-
- resdom->resno = 1;
- resdom->restype = ((Oper *)(exp->oper))->opresulttype;
- resdom->restypmod = -1;
- resdom->resname = pstrdup("<noname>");
- resdom->reskey = 0;
- resdom->reskeyop = 0;
- resdom->resjunk = 0;
-
- tle->resdom = resdom;
- tle->expr = (Node *)aggref;
-
- subqual = copyObject(parsetree->qual);
- modifyAggrefDropQual((Node **)&subqual, (Node *)parsetree->qual, origexp);
-
- sublink = makeNode(SubLink);
- sublink->subLinkType = EXPR_SUBLINK;
- sublink->useor = FALSE;
- sublink->lefthand = lappend(NIL, copyObject(lfirst(exp->args)));
- sublink->oper = lappend(NIL, copyObject(exp));
- sublink->subselect = NULL;
-
- subquery = makeNode(Query);
- sublink->subselect = (Node *)subquery;
-
- subquery->commandType = CMD_SELECT;
- subquery->utilityStmt = NULL;
- subquery->resultRelation = 0;
- subquery->into = NULL;
- subquery->isPortal = FALSE;
- subquery->isBinary = FALSE;
- subquery->isTemp = FALSE;
- subquery->unionall = FALSE;
- subquery->uniqueFlag = NULL;
- subquery->sortClause = NULL;
- subquery->rtable = lappend(NIL, rte);
- subquery->targetList = lappend(NIL, tle);
- subquery->qual = subqual;
- subquery->groupClause = NIL;
- subquery->havingQual = NULL;
- subquery->hasAggs = TRUE;
- subquery->hasSubLinks = FALSE;
- subquery->unionClause = NULL;
-
-
- modifyAggrefUplevel((Node *)sublink);
-
- modifyAggrefChangeVarnodes((Node **)&(sublink->lefthand), target->varno,
- 1, target->varlevelsup);
- modifyAggrefChangeVarnodes((Node **)&(sublink->oper), target->varno,
- 1, target->varlevelsup);
- modifyAggrefChangeVarnodes((Node **)&(sublink->subselect), target->varno,
- 1, target->varlevelsup);
-
- return sublink;
+ /*
+ * Do NOT recurse into Query nodes, because fireRIRrules already
+ * processed subselects of subselects for us.
+ */
+ return expression_tree_walker(node, fireRIRonSubLink,
+ (void *) activeRIRs);
}
/*
- * modifyAggrefQual -
- * Search for qualification expressions that contain aggregate
- * functions and substiture them by sublinks. These expressions
- * originally come from qualifications that use aggregate columns
- * of a view.
+ * fireRIRrules -
+ * Apply all RIR rules on each rangetable entry in a query
*/
-static void
-modifyAggrefQual(Node **nodePtr, Query *parsetree)
+static Query *
+fireRIRrules(Query *parsetree, List *activeRIRs)
{
- Node *node = *nodePtr;
-
- if (node == NULL)
- return;
-
- switch(nodeTag(node)) {
- case T_Var:
- break;
-
- case T_Param:
- break;
+ int rt_index;
- case T_Const:
- break;
-
- case T_GroupClause:
- {
- GroupClause *grp = (GroupClause *)node;
-
- modifyAggrefQual(
- (Node **)(&(grp->entry)),
- parsetree);
- }
- break;
-
- case T_Expr:
- {
- Expr *exp = (Expr *)node;
- SubLink *sub;
-
-
- if (length(exp->args) != 2) {
- modifyAggrefQual(
- (Node **)(&(exp->args)),
- parsetree);
- break;
- }
-
- if (nodeTag(nth(0, exp->args)) != T_Aggref &&
- nodeTag(nth(1, exp->args)) != T_Aggref) {
-
- modifyAggrefQual(
- (Node **)(&(exp->args)),
- parsetree);
- break;
- }
-
- sub = modifyAggrefMakeSublink(exp,
- parsetree);
-
- *nodePtr = (Node *)sub;
- parsetree->hasSubLinks = TRUE;
- }
- break;
-
- case T_CaseExpr:
- {
- /* We're calling recursively,
- * and this routine knows how to handle lists
- * so let it do the work to handle the WHEN clauses... */
- modifyAggrefQual(
- (Node **)(&(((CaseExpr *)node)->args)),
- parsetree);
-
- modifyAggrefQual(
- (Node **)(&(((CaseExpr *)node)->defresult)),
- parsetree);
- }
- break;
-
- case T_CaseWhen:
- {
- modifyAggrefQual(
- (Node **)(&(((CaseWhen *)node)->expr)),
- parsetree);
-
- modifyAggrefQual(
- (Node **)(&(((CaseWhen *)node)->result)),
- parsetree);
- }
- break;
-
- case T_Iter:
- {
- Iter *iter = (Iter *)node;
-
- modifyAggrefQual(
- (Node **)(&(iter->iterexpr)),
- parsetree);
- }
- break;
-
- case T_ArrayRef:
- {
- ArrayRef *ref = (ArrayRef *)node;
-
- modifyAggrefQual(
- (Node **)(&(ref->refupperindexpr)),
- parsetree);
- modifyAggrefQual(
- (Node **)(&(ref->reflowerindexpr)),
- parsetree);
- modifyAggrefQual(
- (Node **)(&(ref->refexpr)),
- parsetree);
- modifyAggrefQual(
- (Node **)(&(ref->refassgnexpr)),
- parsetree);
- }
- break;
-
- case T_List:
- {
- List *l;
-
- foreach (l, (List *)node)
- modifyAggrefQual(
- (Node **)(&(lfirst(l))),
- parsetree);
- }
- break;
-
- case T_SubLink:
- {
- SubLink *sub = (SubLink *)node;
-
- modifyAggrefQual(
- (Node **)(&(sub->subselect)),
- (Query *)(sub->subselect));
- }
- break;
+ /*
+ * 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))
+ {
+ RangeTblEntry *rte;
+ Relation rel;
+ List *locks;
+ RuleLock *rules;
+ RewriteRule *rule;
+ LOCKMODE lockmode;
+ bool relIsUsed;
+ int i;
- case T_Query:
- {
- Query *qry = (Query *)node;
+ ++rt_index;
- modifyAggrefQual(
- (Node **)(&(qry->qual)),
- parsetree);
+ rte = rt_fetch(rt_index, parsetree->rtable);
- modifyAggrefQual(
- (Node **)(&(qry->havingQual)),
- parsetree);
- }
- break;
+ /*
+ * 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);
+ continue;
+ }
- default:
- elog(NOTICE, "unknown node tag %d in modifyAggrefQual()", nodeTag(node));
- elog(NOTICE, "Node is: %s", nodeToString(node));
- break;
-
-
- }
-}
-
-
-static Node *
-FindMatchingTLEntry(List *tlist, char *e_attname)
-{
- List *i;
-
- foreach(i, tlist)
- {
- TargetEntry *tle = lfirst(i);
- char *resname;
-
- resname = tle->resdom->resname;
- if (!strcmp(e_attname, resname))
- return (tle->expr);
- }
- return NULL;
-}
-
-
-static Node *
-make_null(Oid type)
-{
- Const *c = makeNode(Const);
-
- c->consttype = type;
- c->constlen = get_typlen(type);
- c->constvalue = PointerGetDatum(NULL);
- c->constisnull = true;
- c->constbyval = get_typbyval(type);
- return (Node *) c;
-}
-
-
-static void
-apply_RIR_adjust_sublevel(Node *node, int sublevels_up)
-{
- if (node == NULL)
- return;
-
- switch(nodeTag(node)) {
- case T_TargetEntry:
- {
- TargetEntry *tle = (TargetEntry *)node;
-
- apply_RIR_adjust_sublevel(
- (Node *)(tle->expr),
- sublevels_up);
- }
- break;
-
- case T_Aggref:
- {
- Aggref *aggref = (Aggref *)node;
-
- apply_RIR_adjust_sublevel(
- (Node *)(aggref->target),
- sublevels_up);
- }
- break;
-
- case T_GroupClause:
- {
- GroupClause *grp = (GroupClause *)node;
-
- apply_RIR_adjust_sublevel(
- (Node *)(grp->entry),
- sublevels_up);
- }
- break;
-
- case T_Expr:
- {
- Expr *exp = (Expr *)node;
-
- apply_RIR_adjust_sublevel(
- (Node *)(exp->args),
- sublevels_up);
- }
- break;
-
- case T_Iter:
- {
- Iter *iter = (Iter *)node;
-
- apply_RIR_adjust_sublevel(
- (Node *)(iter->iterexpr),
- sublevels_up);
- }
- break;
-
- case T_ArrayRef:
- {
- ArrayRef *ref = (ArrayRef *)node;
-
- apply_RIR_adjust_sublevel(
- (Node *)(ref->refupperindexpr),
- sublevels_up);
-
- apply_RIR_adjust_sublevel(
- (Node *)(ref->reflowerindexpr),
- sublevels_up);
-
- apply_RIR_adjust_sublevel(
- (Node *)(ref->refexpr),
- sublevels_up);
-
- apply_RIR_adjust_sublevel(
- (Node *)(ref->refassgnexpr),
- sublevels_up);
- }
- break;
-
- case T_Var:
- {
- Var *var = (Var *)node;
-
- var->varlevelsup = sublevels_up;
- }
- break;
-
- case T_Param:
- break;
-
- case T_Const:
- break;
-
- case T_List:
- {
- List *l;
-
- foreach (l, (List *)node) {
- apply_RIR_adjust_sublevel(
- (Node *)lfirst(l),
- sublevels_up);
- }
- }
- break;
-
- case T_CaseExpr:
- {
- CaseExpr *exp = (CaseExpr *)node;
-
- apply_RIR_adjust_sublevel(
- (Node *)(exp->args),
- sublevels_up);
-
- apply_RIR_adjust_sublevel(
- (Node *)(exp->defresult),
- sublevels_up);
- }
- break;
-
- case T_CaseWhen:
- {
- CaseWhen *exp = (CaseWhen *)node;
-
- apply_RIR_adjust_sublevel(
- (Node *)(exp->expr),
- sublevels_up);
-
- apply_RIR_adjust_sublevel(
- (Node *)(exp->result),
- sublevels_up);
- }
- break;
-
- default:
- elog(NOTICE, "unknown node tag %d in attribute_used()", nodeTag(node));
- elog(NOTICE, "Node is: %s", nodeToString(node));
- break;
-
-
- }
-}
-
-
-static void
-apply_RIR_view(Node **nodePtr, int rt_index, RangeTblEntry *rte, List *tlist, int *modified, int sublevels_up)
-{
- Node *node = *nodePtr;
-
- if (node == NULL)
- return;
-
- switch(nodeTag(node)) {
- case T_TargetEntry:
- {
- TargetEntry *tle = (TargetEntry *)node;
-
- apply_RIR_view(
- (Node **)(&(tle->expr)),
- rt_index,
- rte,
- tlist,
- modified,
- sublevels_up);
- }
- break;
-
- case T_Aggref:
- {
- Aggref *aggref = (Aggref *)node;
-
- apply_RIR_view(
- (Node **)(&(aggref->target)),
- rt_index,
- rte,
- tlist,
- modified,
- sublevels_up);
- }
- break;
-
- case T_GroupClause:
- {
- GroupClause *grp = (GroupClause *)node;
-
- apply_RIR_view(
- (Node **)(&(grp->entry)),
- rt_index,
- rte,
- tlist,
- modified,
- sublevels_up);
- }
- break;
-
- case T_Expr:
- {
- Expr *exp = (Expr *)node;
-
- apply_RIR_view(
- (Node **)(&(exp->args)),
- rt_index,
- rte,
- tlist,
- modified,
- sublevels_up);
- }
- break;
-
- case T_Iter:
- {
- Iter *iter = (Iter *)node;
-
- apply_RIR_view(
- (Node **)(&(iter->iterexpr)),
- rt_index,
- rte,
- tlist,
- modified,
- sublevels_up);
- }
- break;
-
- case T_ArrayRef:
- {
- ArrayRef *ref = (ArrayRef *)node;
-
- apply_RIR_view(
- (Node **)(&(ref->refupperindexpr)),
- rt_index,
- rte,
- tlist,
- modified,
- sublevels_up);
- apply_RIR_view(
- (Node **)(&(ref->reflowerindexpr)),
- rt_index,
- rte,
- tlist,
- modified,
- sublevels_up);
- apply_RIR_view(
- (Node **)(&(ref->refexpr)),
- rt_index,
- rte,
- tlist,
- modified,
- sublevels_up);
- apply_RIR_view(
- (Node **)(&(ref->refassgnexpr)),
- rt_index,
- rte,
- tlist,
- modified,
- sublevels_up);
- }
- break;
-
- case T_Var:
- {
- Var *var = (Var *)node;
-
- if (var->varlevelsup == sublevels_up &&
- var->varno == rt_index) {
- Node *exp;
-
- if (var->varattno < 0)
- elog(ERROR, "system column %s not available - %s is a view", get_attname(rte->relid, var->varattno), rte->relname);
- exp = FindMatchingTLEntry(
- tlist,
- get_attname(rte->relid,
- var->varattno));
-
- if (exp == NULL) {
- *nodePtr = make_null(var->vartype);
- return;
- }
-
- exp = copyObject(exp);
- if (var->varlevelsup > 0)
- apply_RIR_adjust_sublevel(exp, var->varlevelsup);
- *nodePtr = exp;
- *modified = TRUE;
- }
- }
- break;
-
- case T_Param:
- break;
-
- case T_Const:
- break;
-
- case T_List:
- {
- List *l;
-
- foreach (l, (List *)node)
- apply_RIR_view(
- (Node **)(&(lfirst(l))),
- rt_index,
- rte,
- tlist,
- modified,
- sublevels_up);
- }
- break;
-
- case T_SubLink:
- {
- SubLink *sub = (SubLink *)node;
- List *tmp_lefthand, *tmp_oper;
-
- apply_RIR_view(
- (Node **)(&(sub->lefthand)),
- rt_index,
- rte,
- tlist,
- modified,
- sublevels_up);
-
- apply_RIR_view(
- (Node **)(&(sub->subselect)),
- rt_index,
- rte,
- tlist,
- modified,
- sublevels_up + 1);
-
- /***S*I***/
- tmp_lefthand = sub->lefthand;
- foreach(tmp_oper, sub->oper)
- {
- lfirst(((Expr *) lfirst(tmp_oper))->args) =
- lfirst(tmp_lefthand);
- tmp_lefthand = lnext(tmp_lefthand);
- }
- }
- break;
-
- case T_Query:
- {
- Query *qry = (Query *)node;
-
- apply_RIR_view(
- (Node **)(&(qry->targetList)),
- rt_index,
- rte,
- tlist,
- modified,
- sublevels_up);
-
- apply_RIR_view(
- (Node **)(&(qry->qual)),
- rt_index,
- rte,
- tlist,
- modified,
- sublevels_up);
-
- apply_RIR_view(
- (Node **)(&(qry->havingQual)),
- rt_index,
- rte,
- tlist,
- modified,
- sublevels_up);
-
- apply_RIR_view(
- (Node **)(&(qry->groupClause)),
- rt_index,
- rte,
- tlist,
- modified,
- sublevels_up);
- }
- break;
-
- case T_CaseExpr:
- {
- CaseExpr *exp = (CaseExpr *)node;
-
- apply_RIR_view(
- (Node **)(&(exp->args)),
- rt_index,
- rte,
- tlist,
- modified,
- sublevels_up);
-
- apply_RIR_view(
- (Node **)(&(exp->defresult)),
- rt_index,
- rte,
- tlist,
- modified,
- sublevels_up);
- }
- break;
-
- case T_CaseWhen:
- {
- CaseWhen *exp = (CaseWhen *)node;
-
- apply_RIR_view(
- (Node **)(&(exp->expr)),
- rt_index,
- rte,
- tlist,
- modified,
- sublevels_up);
-
- apply_RIR_view(
- (Node **)(&(exp->result)),
- rt_index,
- rte,
- tlist,
- modified,
- sublevels_up);
- }
- break;
-
- default:
- elog(NOTICE, "unknown node tag %d in apply_RIR_view()", nodeTag(node));
- elog(NOTICE, "Node is: %s", nodeToString(node));
- break;
- }
-}
-
-extern void CheckSelectForUpdate(Query *rule_action); /* in analyze.c */
-
-static void
-ApplyRetrieveRule(Query *parsetree,
- RewriteRule *rule,
- int rt_index,
- int relation_level,
- Relation relation,
- int *modified)
-{
- Query *rule_action = NULL;
- Node *rule_qual;
- List *rtable,
- *rt,
- *l;
- int nothing,
- rt_length;
- int badsql = FALSE;
-
- rule_qual = rule->qual;
- if (rule->actions)
- {
- if (length(rule->actions) > 1) /* ??? because we don't handle
- * rules with more than one
- * action? -ay */
-
- return;
- rule_action = copyObject(lfirst(rule->actions));
- nothing = FALSE;
- }
- else
- nothing = TRUE;
-
- rtable = copyObject(parsetree->rtable);
- foreach(rt, rtable)
- {
- RangeTblEntry *rte = lfirst(rt);
+ /*
+ * Joins and other non-relation RTEs can be ignored completely.
+ */
+ if (rte->rtekind != RTE_RELATION)
+ continue;
/*
- * this is to prevent add_missing_vars_to_base_rels() from adding
- * a bogus entry to the new target list.
- */
- rte->inFromCl = false;
- }
- rt_length = length(rtable);
-
- rtable = nconc(rtable, copyObject(rule_action->rtable));
- parsetree->rtable = rtable;
-
- /* FOR UPDATE of view... */
- foreach (l, parsetree->rowMark)
- {
- if (((RowMark*)lfirst(l))->rti == rt_index)
- break;
- }
- if (l != NULL) /* oh, hell -:) */
- {
- RowMark *newrm;
- Index rti = 1;
- List *l2;
-
- CheckSelectForUpdate(rule_action);
- /*
- * We believe that rt_index is VIEW - nothing should be
- * marked for VIEW, but ACL check must be done.
- * As for real tables of VIEW - their rows must be marked, but
- * we have to skip ACL check for them.
- */
- ((RowMark*)lfirst(l))->info &= ~ROW_MARK_FOR_UPDATE;
- foreach (l2, rule_action->rtable)
- {
- /*
- * RTable of VIEW has two entries of VIEW itself -
- * we use relid to skip them.
- */
- if (relation->rd_id != ((RangeTblEntry*)lfirst(l2))->relid)
- {
- newrm = makeNode(RowMark);
- newrm->rti = rti + rt_length;
- newrm->info = ROW_MARK_FOR_UPDATE;
- lnext(l) = lcons(newrm, lnext(l));
- l = lnext(l);
- }
- rti++;
- }
- }
-
- rule_action->rtable = rtable;
- OffsetVarNodes((Node *) rule_qual, rt_length, 0);
- OffsetVarNodes((Node *) rule_action, rt_length, 0);
-
- ChangeVarNodes((Node *) rule_qual,
- PRS2_CURRENT_VARNO + rt_length, rt_index, 0);
- ChangeVarNodes((Node *) rule_action,
- PRS2_CURRENT_VARNO + rt_length, rt_index, 0);
-
- if (relation_level)
- {
- apply_RIR_view((Node **) &parsetree, rt_index,
- (RangeTblEntry *)nth(rt_index - 1, rtable),
- rule_action->targetList, modified, 0);
- apply_RIR_view((Node **) &rule_action, rt_index,
- (RangeTblEntry *)nth(rt_index - 1, rtable),
- rule_action->targetList, modified, 0);
- }
- else
- {
- HandleRIRAttributeRule(parsetree, rtable, rule_action->targetList,
- rt_index, rule->attrno, modified, &badsql);
- }
- if (*modified && !badsql) {
- AddQual(parsetree, rule_action->qual);
- /* This will only work if the query made to the view defined by the following
- * groupClause groups by the same attributes or does not use group at all! */
- if (parsetree->groupClause == NULL)
- parsetree->groupClause=rule_action->groupClause;
- AddHavingQual(parsetree, rule_action->havingQual);
- parsetree->hasAggs = (rule_action->hasAggs || parsetree->hasAggs);
- parsetree->hasSubLinks = (rule_action->hasSubLinks || parsetree->hasSubLinks);
- }
-}
-
-
-static void
-fireRIRonSubselect(Node *node)
-{
- if (node == NULL)
- return;
-
- switch(nodeTag(node)) {
- case T_TargetEntry:
- {
- TargetEntry *tle = (TargetEntry *)node;
-
- fireRIRonSubselect(
- (Node *)(tle->expr));
- }
- break;
-
- case T_Aggref:
- {
- Aggref *aggref = (Aggref *)node;
-
- fireRIRonSubselect(
- (Node *)(aggref->target));
- }
- break;
-
- case T_GroupClause:
- {
- GroupClause *grp = (GroupClause *)node;
-
- fireRIRonSubselect(
- (Node *)(grp->entry));
- }
- break;
-
- case T_Expr:
- {
- Expr *exp = (Expr *)node;
-
- fireRIRonSubselect(
- (Node *)(exp->args));
- }
- break;
-
- case T_Iter:
- {
- Iter *iter = (Iter *)node;
-
- fireRIRonSubselect(
- (Node *)(iter->iterexpr));
- }
- break;
-
- case T_ArrayRef:
- {
- ArrayRef *ref = (ArrayRef *)node;
-
- fireRIRonSubselect(
- (Node *)(ref->refupperindexpr));
- fireRIRonSubselect(
- (Node *)(ref->reflowerindexpr));
- fireRIRonSubselect(
- (Node *)(ref->refexpr));
- fireRIRonSubselect(
- (Node *)(ref->refassgnexpr));
- }
- break;
-
- case T_Var:
- break;
-
- case T_Param:
- break;
-
- case T_Const:
- break;
-
- case T_List:
- {
- List *l;
-
- foreach (l, (List *)node)
- fireRIRonSubselect(
- (Node *)(lfirst(l)));
- }
- break;
-
- case T_SubLink:
- {
- SubLink *sub = (SubLink *)node;
- Query *qry;
-
- fireRIRonSubselect(
- (Node *)(sub->lefthand));
-
- qry = fireRIRrules((Query *)(sub->subselect));
-
- fireRIRonSubselect(
- (Node *)qry);
-
- sub->subselect = (Node *) qry;
- }
- break;
-
- case T_CaseExpr:
- {
- CaseExpr *exp = (CaseExpr *)node;
-
- fireRIRonSubselect(
- (Node *)(exp->args));
-
- fireRIRonSubselect(
- (Node *)(exp->defresult));
- }
- break;
-
- case T_CaseWhen:
- {
- CaseWhen *exp = (CaseWhen *)node;
-
- fireRIRonSubselect(
- (Node *)(exp->expr));
-
- fireRIRonSubselect(
- (Node *)(exp->result));
- }
- break;
-
- case T_Query:
- {
- Query *qry = (Query *)node;
-
- fireRIRonSubselect(
- (Node *)(qry->targetList));
-
- fireRIRonSubselect(
- (Node *)(qry->qual));
-
- fireRIRonSubselect(
- (Node *)(qry->havingQual));
-
- fireRIRonSubselect(
- (Node *)(qry->groupClause));
- }
- break;
-
- default:
- elog(NOTICE, "unknown node tag %d in fireRIRonSubselect()", nodeTag(node));
- elog(NOTICE, "Node is: %s", nodeToString(node));
- break;
-
-
- }
-}
-
-
-/*
- * fireRIRrules -
- * Apply all RIR rules on each rangetable entry in a query
- */
-static Query *
-fireRIRrules(Query *parsetree)
-{
- int rt_index;
- RangeTblEntry *rte;
- Relation rel;
- List *locks;
- RuleLock *rules;
- RewriteRule *rule;
- RewriteRule RIRonly;
- int modified;
- int i;
- List *l;
-
- rt_index = 0;
- while(rt_index < length(parsetree->rtable)) {
- ++rt_index;
+ * 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.
+ */
+ relIsUsed = rangeTableEntry_used((Node *) parsetree, rt_index, 0);
- if (!rangeTableEntry_used((Node *)parsetree, rt_index, 0))
- continue;
-
- rte = nth(rt_index - 1, parsetree->rtable);
- rel = heap_openr(rte->relname);
- if (rel->rd_rules == NULL) {
- heap_close(rel);
+ if (!relIsUsed && rt_index != parsetree->resultRelation)
continue;
- }
- rules = rel->rd_rules;
- locks = NIL;
+ /*
+ * 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.
+ */
+ if (rt_index == parsetree->resultRelation)
+ lockmode = NoLock;
+ else if (intMember(rt_index, parsetree->rowMarks))
+ lockmode = RowShareLock;
+ else
+ lockmode = AccessShareLock;
+
+ rel = heap_open(rte->relid, lockmode);
/*
* Collect the RIR rules that we must apply
*/
- for (i = 0; i < rules->numLocks; i++) {
+ rules = rel->rd_rules;
+ if (rules == NULL)
+ {
+ heap_close(rel, NoLock);
+ continue;
+ }
+ locks = NIL;
+ for (i = 0; i < rules->numLocks; i++)
+ {
rule = rules->rules[i];
if (rule->event != CMD_SELECT)
continue;
-
- if (rule->attrno > 0 &&
- !attribute_used((Node *)parsetree,
- rt_index,
- rule->attrno, 0))
- continue;
+
+ if (rule->attrno > 0)
+ {
+ /* per-attr rule; do we need it? */
+ if (!attribute_used((Node *) parsetree, rt_index,
+ rule->attrno, 0))
+ continue;
+ }
locks = lappend(locks, rule);
}
/*
- * Check permissions
+ * If we found any, apply them --- but first check for recursion!
*/
- checkLockPerms(locks, parsetree, rt_index);
+ if (locks != NIL)
+ {
+ List *newActiveRIRs;
+ List *l;
- /*
- * Now apply them
- */
- foreach (l, locks) {
- rule = lfirst(l);
-
- RIRonly.event = rule->event;
- RIRonly.attrno = rule->attrno;
- RIRonly.qual = rule->qual;
- RIRonly.actions = rule->actions;
-
- ApplyRetrieveRule(parsetree,
- &RIRonly,
- rt_index,
- RIRonly.attrno == -1,
- rel,
- &modified);
+ if (oidMember(RelationGetRelid(rel), activeRIRs))
+ elog(ERROR, "Infinite recursion detected in rules for relation %s",
+ RelationGetRelationName(rel));
+ newActiveRIRs = lconso(RelationGetRelid(rel), activeRIRs);
+
+ foreach(l, locks)
+ {
+ rule = lfirst(l);
+
+ parsetree = ApplyRetrieveRule(parsetree,
+ rule,
+ rt_index,
+ rule->attrno == -1,
+ rel,
+ relIsUsed,
+ newActiveRIRs);
+ }
}
- heap_close(rel);
+ heap_close(rel, NoLock);
}
- fireRIRonSubselect((Node *) parsetree);
- modifyAggrefQual((Node **) &(parsetree->qual), parsetree);
+ /*
+ * 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);
+
+ /*
+ * 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...)
+ */
+ if (parsetree->hasAggs)
+ {
+ parsetree->hasAggs = checkExprHasAggs((Node *) parsetree);
+ if (parsetree->hasAggs)
+ if (checkExprHasAggs((Node *) parsetree->jointree))
+ elog(ERROR, "fireRIRrules: failed to remove aggs from qual");
+ }
+ if (parsetree->hasSubLinks)
+ parsetree->hasSubLinks = checkExprHasSubLink((Node *) parsetree);
return parsetree;
}
/*
- * idea is to fire regular rules first, then qualified instead
- * rules and unqualified instead rules last. Any lemming is counted for.
+ * Modify the given query by adding 'AND rule_qual IS NOT TRUE' to its
+ * qualification. This is used to generate suitable "else clauses" for
+ * conditional INSTEAD rules. (Unfortunately we must use "x IS NOT TRUE",
+ * not just "NOT x" which the planner is much smarter about, else we will
+ * do the wrong thing when the qual evaluates to NULL.)
+ *
+ * The rule_qual may contain references to OLD or NEW. OLD references are
+ * replaced by references to the specified rt_index (the relation that the
+ * rule applies to). NEW references are only possible for INSERT and UPDATE
+ * queries on the relation itself, and so they should be replaced by copies
+ * of the related entries in the query's own targetlist.
*/
-static List *
-orderRules(List *locks)
-{
- List *regular = NIL;
- List *instead_rules = NIL;
- List *instead_qualified = NIL;
- List *i;
-
- foreach(i, locks)
- {
- RewriteRule *rule_lock = (RewriteRule *) lfirst(i);
-
- if (rule_lock->isInstead)
- {
- if (rule_lock->qual == NULL)
- instead_rules = lappend(instead_rules, rule_lock);
- else
- instead_qualified = lappend(instead_qualified, rule_lock);
- }
- else
- regular = lappend(regular, rule_lock);
- }
- regular = nconc(regular, instead_qualified);
- return nconc(regular, instead_rules);
-}
-
-
-
static Query *
-CopyAndAddQual(Query *parsetree,
- List *actions,
- Node *rule_qual,
- int rt_index,
- CmdType event)
+CopyAndAddInvertedQual(Query *parsetree,
+ Node *rule_qual,
+ int rt_index,
+ CmdType event)
{
Query *new_tree = (Query *) copyObject(parsetree);
- Node *new_qual = NULL;
- Query *rule_action = NULL;
-
- if (actions)
- rule_action = lfirst(actions);
- if (rule_qual != NULL)
- new_qual = (Node *) copyObject(rule_qual);
- if (rule_action != NULL)
- {
- List *rtable;
- int rt_length;
-
- rtable = new_tree->rtable;
- rt_length = length(rtable);
- rtable = nconc(rtable, copyObject(rule_action->rtable));
- new_tree->rtable = rtable;
- OffsetVarNodes(new_qual, rt_length, 0);
- ChangeVarNodes(new_qual, PRS2_CURRENT_VARNO + rt_length, rt_index, 0);
- }
- /* XXX -- where current doesn't work for instead nothing.... yet */
- AddNotQual(new_tree, new_qual);
+ Node *new_qual = (Node *) copyObject(rule_qual);
+
+ /* Fix references to OLD */
+ ChangeVarNodes(new_qual, PRS2_OLD_VARNO, rt_index, 0);
+ /* Fix references to NEW */
+ if (event == CMD_INSERT || event == CMD_UPDATE)
+ new_qual = ResolveNew(new_qual,
+ PRS2_NEW_VARNO,
+ 0,
+ parsetree->targetList,
+ event,
+ rt_index);
+ /* And attach the fixed qual */
+ AddInvertedQual(new_tree, new_qual);
return new_tree;
}
-
/*
* fireRules -
* Iterate through rule locks applying rules.
- * All rules create their own parsetrees. Instead rules
- * with rule qualification save the original parsetree
- * and add their negated qualification to it. Real instead
- * rules finally throw away the original parsetree.
*
- * remember: reality is for dead birds -- glass
+ * Input arguments:
+ * parsetree - original query
+ * rt_index - RT index of result relation in original query
+ * event - type of rule event
+ * locks - list of rules to fire
+ * Output arguments:
+ * *instead_flag - set TRUE if any unqualified INSTEAD rule is found
+ * (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:
+ * list of rule actions adjusted for use with this query
*
+ * Qualified INSTEAD rules generate their action with the qualification
+ * condition added. They also generate a modified version of the original
+ * query with the negated qualification added, so that it will run only for
+ * rows that the qualified action doesn't act on. (If there are multiple
+ * qualified INSTEAD rules, we AND all the negated quals onto a single
+ * modified original query.) We won't execute the original, unmodified
+ * query if we find either qualified or unqualified INSTEAD rules. If
+ * we find both, the modified original query is discarded too.
*/
static List *
fireRules(Query *parsetree,
int rt_index,
CmdType event,
- bool *instead_flag,
List *locks,
- List **qual_products)
+ bool *instead_flag,
+ Query **qual_product)
{
- RewriteInfo *info;
List *results = NIL;
List *i;
- /* choose rule to fire from list of rules */
- if (locks == NIL)
- {
- return NIL;
- }
-
- locks = orderRules(locks); /* real instead rules last */
foreach(i, locks)
{
RewriteRule *rule_lock = (RewriteRule *) lfirst(i);
- Node *qual,
- *event_qual;
- List *actions;
+ Node *event_qual = rule_lock->qual;
+ List *actions = rule_lock->actions;
+ QuerySource qsrc;
List *r;
- /*
- * Instead rules change the resultRelation of the query. So the
- * permission checks on the initial resultRelation would never be
- * done (this is normally done in the executor deep down). So we
- * must do it here. The result relations resulting from earlier
- * rewrites are already checked against the rules eventrelation
- * owner (during matchLocks) and have the skipAcl flag set.
- */
- if (rule_lock->isInstead &&
- parsetree->commandType != CMD_SELECT)
+ /* Determine correct QuerySource value for actions */
+ if (rule_lock->isInstead)
{
- RangeTblEntry *rte;
- int32 acl_rc;
- int32 reqperm;
-
- switch (parsetree->commandType)
- {
- case CMD_INSERT:
- reqperm = ACL_AP;
- break;
- default:
- reqperm = ACL_WR;
- break;
- }
-
- rte = (RangeTblEntry *) nth(parsetree->resultRelation - 1,
- parsetree->rtable);
- if (!rte->skipAcl)
+ if (event_qual != NULL)
+ qsrc = QSRC_QUAL_INSTEAD_RULE;
+ else
{
- acl_rc = pg_aclcheck(rte->relname,
- GetPgUserName(), reqperm);
- if (acl_rc != ACLCHECK_OK)
- {
- elog(ERROR, "%s: %s",
- rte->relname,
- aclcheck_error_strings[acl_rc]);
- }
+ qsrc = QSRC_INSTEAD_RULE;
+ *instead_flag = true; /* report unqualified INSTEAD */
}
}
+ else
+ qsrc = QSRC_NON_INSTEAD_RULE;
- /* multiple rule action time */
- *instead_flag = rule_lock->isInstead;
- event_qual = rule_lock->qual;
- actions = rule_lock->actions;
- if (event_qual != NULL && *instead_flag)
+ if (qsrc == QSRC_QUAL_INSTEAD_RULE)
{
- Query *qual_product;
- RewriteInfo qual_info;
-
- /* ----------
- * 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 it's 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_products
- * so deepRewriteQuery() 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 (*qual_products == NIL)
- qual_product = parsetree;
- else
- qual_product = (Query *) nth(0, *qual_products);
-
- qual_info.event = qual_product->commandType;
- qual_info.new_varno = length(qual_product->rtable) + 2;
- qual_product = CopyAndAddQual(qual_product,
- actions,
- event_qual,
- rt_index,
- event);
-
- qual_info.rule_action = qual_product;
-
- if (event == CMD_INSERT || event == CMD_UPDATE)
- FixNew(&qual_info, qual_product);
-
- *qual_products = lappend(NIL, qual_product);
+ if (! *instead_flag)
+ {
+ if (*qual_product == NULL)
+ *qual_product = parsetree;
+ *qual_product = CopyAndAddInvertedQual(*qual_product,
+ event_qual,
+ rt_index,
+ event);
+ }
}
+ /* Now process the rule's actions and add them to the result list */
foreach(r, actions)
{
Query *rule_action = lfirst(r);
- Node *rule_qual = copyObject(event_qual);
if (rule_action->commandType == CMD_NOTHING)
continue;
- /*--------------------------------------------------
- * We copy the qualifications of the parsetree
- * to the action and vice versa. So force
- * hasSubLinks if one of them has it.
- *
- * As of 6.4 only parsetree qualifications can
- * have sublinks. If this changes, we must make
- * this a node lookup at the end of rewriting.
- *
- * Jan
- *--------------------------------------------------
- */
- if (parsetree->hasSubLinks && !rule_action->hasSubLinks)
- {
- rule_action = copyObject(rule_action);
- rule_action->hasSubLinks = TRUE;
- }
- if (!parsetree->hasSubLinks && rule_action->hasSubLinks)
- {
- parsetree->hasSubLinks = TRUE;
- }
-
- /*--------------------------------------------------
- * Step 1:
- * Rewrite current.attribute or current to tuple variable
- * this appears to be done in parser?
- *--------------------------------------------------
- */
- info = gatherRewriteMeta(parsetree, rule_action, rule_qual,
- rt_index, event, instead_flag);
-
- /* handle escapable cases, or those handled by other code */
- if (info->nothing)
- {
- if (*instead_flag)
- return NIL;
- else
- continue;
- }
-
- if (info->action == info->event &&
- info->event == CMD_SELECT)
- continue;
-
- /*
- * Event Qualification forces copying of parsetree and
- * splitting into two queries one w/rule_qual, one w/NOT
- * rule_qual. Also add user query qual onto rule action
- */
- qual = parsetree->qual;
- AddQual(info->rule_action, qual);
-
- if (info->rule_qual != NULL)
- AddQual(info->rule_action, info->rule_qual);
-
- /*--------------------------------------------------
- * Step 2:
- * Rewrite new.attribute w/ right hand side of target-list
- * entry for appropriate field name in insert/update
- *--------------------------------------------------
- */
- if ((info->event == CMD_INSERT) || (info->event == CMD_UPDATE))
- FixNew(info, parsetree);
+ rule_action = rewriteRuleAction(parsetree, rule_action,
+ event_qual, rt_index, event);
- /*--------------------------------------------------
- * Step 3:
- * rewriting due to retrieve rules
- *--------------------------------------------------
- */
- info->rule_action->rtable = info->rt;
- /*
- ProcessRetrieveQuery(info->rule_action, info->rt,
- &orig_instead_flag, TRUE);
- */
-
- /*--------------------------------------------------
- * Step 4
- * Simplify? hey, no algorithm for simplification... let
- * the planner do it.
- *--------------------------------------------------
- */
- results = lappend(results, info->rule_action);
+ rule_action->querySource = qsrc;
+ rule_action->canSetTag = false; /* might change later */
- pfree(info);
+ results = lappend(results, rule_action);
}
-
- /* ----------
- * If this was an unqualified instead rule,
- * throw away an eventually saved 'default' parsetree
- * ----------
- */
- if (event_qual == NULL && *instead_flag)
- *qual_products = NIL;
}
+
return results;
}
-
+/*
+ * RewriteQuery -
+ * rewrites the query and apply the rules again on the queries rewritten
+ *
+ * rewrite_events is a list of open query-rewrite actions, so we can detect
+ * infinite recursion.
+ */
static List *
-RewriteQuery(Query *parsetree, bool *instead_flag, List **qual_products)
+RewriteQuery(Query *parsetree, List *rewrite_events)
{
- CmdType event;
- List *product_queries = NIL;
- int result_relation = 0;
- RangeTblEntry *rt_entry;
- Relation rt_entry_relation = NULL;
- RuleLock *rt_entry_locks = NULL;
-
- Assert(parsetree != NULL);
-
- event = parsetree->commandType;
-
- /*
- * SELECT rules are handled later when we have all the
- * queries that should get executed
- */
- if (event == CMD_SELECT)
- return NIL;
-
- /*
- * Utilities aren't rewritten at all - why is this here?
- */
- if (event == CMD_UTILITY)
- return NIL;
-
- /*
- * only for a delete may the targetlist be NULL
- */
- if (event != CMD_DELETE)
- Assert(parsetree->targetList != NULL);
-
- result_relation = parsetree->resultRelation;
+ CmdType event = parsetree->commandType;
+ bool instead = false;
+ Query *qual_product = NULL;
+ List *rewritten = NIL;
/*
- * the statement is an update, insert or delete - fire rules
- * on it.
+ * If the statement is an update, insert or delete - fire 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?)
*/
- rt_entry = rt_fetch(result_relation, parsetree->rtable);
- rt_entry_relation = heap_openr(rt_entry->relname);
- rt_entry_locks = rt_entry_relation->rd_rules;
- heap_close(rt_entry_relation);
-
- if (rt_entry_locks != NULL)
+ if (event != CMD_SELECT && event != CMD_UTILITY)
{
- List *locks = matchLocks(event, rt_entry_locks, result_relation, parsetree);
-
- product_queries = fireRules(parsetree,
- result_relation,
- event,
- instead_flag,
- locks,
- qual_products);
- }
+ int result_relation;
+ RangeTblEntry *rt_entry;
+ Relation rt_entry_relation;
+ List *locks;
- return product_queries;
+ result_relation = parsetree->resultRelation;
+ Assert(result_relation != 0);
+ rt_entry = rt_fetch(result_relation, parsetree->rtable);
+ 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.
+ */
+ rt_entry_relation = heap_open(rt_entry->relid, RowExclusiveLock);
+ /*
+ * 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.
+ */
+ if (event == CMD_INSERT || event == CMD_UPDATE)
+ rewriteTargetList(parsetree, rt_entry_relation);
-/*
- * to avoid infinite recursion, we restrict the number of times a query
- * can be rewritten. Detecting cycles is left for the reader as an excercise.
- */
-#ifndef REWRITE_INVOKE_MAX
-#define REWRITE_INVOKE_MAX 10
-#endif
+ /*
+ * Collect and apply the appropriate rules.
+ */
+ locks = matchLocks(event, rt_entry_relation->rd_rules,
+ result_relation, parsetree);
-static int numQueryRewriteInvoked = 0;
+ if (locks != NIL)
+ {
+ List *product_queries;
-/*
- * deepRewriteQuery -
- * rewrites the query and apply the rules again on the queries rewritten
- */
-static List *
-deepRewriteQuery(Query *parsetree)
-{
- List *n;
- List *rewritten = NIL;
- List *result = NIL;
- bool instead;
- List *qual_products = NIL;
+ product_queries = fireRules(parsetree,
+ result_relation,
+ event,
+ locks,
+ &instead,
+ &qual_product);
+ /*
+ * If we got any product queries, recursively rewrite them
+ * --- but first check for recursion!
+ */
+ if (product_queries != NIL)
+ {
+ List *n;
+ rewrite_event *rev;
+ foreach(n, rewrite_events)
+ {
+ rev = (rewrite_event *) lfirst(n);
+ if (rev->relation == RelationGetRelid(rt_entry_relation) &&
+ rev->event == event)
+ elog(ERROR, "Infinite recursion detected in rules for relation %s",
+ RelationGetRelationName(rt_entry_relation));
+ }
- if (++numQueryRewriteInvoked > REWRITE_INVOKE_MAX)
- {
- elog(ERROR, "query rewritten %d times, may contain cycles",
- numQueryRewriteInvoked - 1);
- }
+ rev = (rewrite_event *) palloc(sizeof(rewrite_event));
+ rev->relation = RelationGetRelid(rt_entry_relation);
+ rev->event = event;
+ rewrite_events = lcons(rev, rewrite_events);
- instead = FALSE;
- result = RewriteQuery(parsetree, &instead, &qual_products);
+ foreach(n, product_queries)
+ {
+ Query *pt = (Query *) lfirst(n);
+ List *newstuff;
- foreach(n, result)
- {
- Query *pt = lfirst(n);
- List *newstuff = NIL;
+ newstuff = RewriteQuery(pt, rewrite_events);
+ rewritten = nconc(rewritten, newstuff);
+ }
+ }
+ }
- newstuff = deepRewriteQuery(pt);
- if (newstuff != NIL)
- rewritten = nconc(rewritten, newstuff);
+ heap_close(rt_entry_relation, NoLock); /* keep lock! */
}
- /* ----------
- * qual_products are the original query with the negated
- * rule qualification of an instead rule
- * ----------
- */
- if (qual_products != NIL)
- rewritten = nconc(rewritten, qual_products);
-
- /* ----------
- * The original query is appended last if not instead
- * 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 execution makes the deleted (and
- * maybe the updated) tuples disappear so the scans
- * for them in the rule actions cannot find them.
- * ----------
- */
- if (!instead)
- rewritten = lappend(rewritten, parsetree);
-
- return rewritten;
-}
-
-
-/*
- * QueryOneRewrite -
- * rewrite one query
- */
-static List *
-QueryRewriteOne(Query *parsetree)
-{
- numQueryRewriteInvoked = 0;
-
/*
- * take a deep breath and apply all the rewrite rules - ay
- */
- return deepRewriteQuery(parsetree);
-}
-
-
-/* ----------
- * RewritePreprocessQuery -
- * adjust details in the parsetree, the rule system
- * depends on
- * ----------
- */
-static void
-RewritePreprocessQuery(Query *parsetree)
-{
- /* ----------
- * if the query has a resultRelation, reassign the
- * result domain numbers to the attribute numbers in the
- * target relation. FixNew() depends on it when replacing
- * *new* references in a rule action by the expressions
- * from the rewritten query.
- * ----------
+ * 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 INSTEADs were found, else the unmodified form.
*/
- if (parsetree->resultRelation > 0)
+ if (!instead)
{
- RangeTblEntry *rte;
- Relation rd;
- List *tl;
- TargetEntry *tle;
- int resdomno;
-
- rte = (RangeTblEntry *) nth(parsetree->resultRelation - 1,
- parsetree->rtable);
- rd = heap_openr(rte->relname);
-
- foreach(tl, parsetree->targetList)
+ if (parsetree->commandType == CMD_INSERT)
{
- tle = (TargetEntry *) lfirst(tl);
- resdomno = attnameAttNum(rd, tle->resdom->resname);
- tle->resdom->resno = resdomno;
+ if (qual_product != NULL)
+ rewritten = lcons(qual_product, rewritten);
+ else
+ rewritten = lcons(parsetree, rewritten);
+ }
+ else
+ {
+ if (qual_product != NULL)
+ rewritten = lappend(rewritten, qual_product);
+ else
+ rewritten = lappend(rewritten, parsetree);
}
-
- heap_close(rd);
}
+
+ return rewritten;
}
/*
* QueryRewrite -
- * rewrite one query via query rewrite system, possibly returning 0
- * or many queries
+ * Primary entry point to the query rewriter.
+ * 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.
*/
List *
QueryRewrite(Query *parsetree)
{
- List *querylist;
- List *results = NIL;
- List *l;
- Query *query;
+ List *querylist;
+ List *results = NIL;
+ List *l;
+ CmdType origCmdType;
+ bool foundOriginalQuery;
+ Query *lastInstead;
/*
* Step 1
*
- * There still seems something broken with the resdom numbers
- * so we reassign them first.
- */
- RewritePreprocessQuery(parsetree);
-
- /*
- * Step 2
- *
* Apply all non-SELECT rules possibly getting 0 or many queries
*/
- querylist = QueryRewriteOne(parsetree);
+ querylist = RewriteQuery(parsetree, NIL);
/*
- * Step 3
+ * Step 2
*
* Apply all the RIR rules on each query
*/
- foreach (l, querylist) {
- query = (Query *)lfirst(l);
- results = lappend(results, fireRIRrules(query));
- }
- return results;
-}
-/***S*I***/
-/* This function takes two targetlists as arguments and checks if the targetlists are compatible
- * (i.e. both select for the same number of attributes and the types are compatible
- */
-void check_targetlists_are_compatible(List *prev_target, List *current_target)
-{
- List *next_target;
-
- if (length(prev_target) !=
- length(current_target))
- elog(ERROR,"Each UNION | EXCEPT | INTERSECT query must have the same number of columns.");
- foreach(next_target, current_target)
- {
- Oid itype;
- Oid otype;
-
- otype = ((TargetEntry *) lfirst(prev_target))->resdom->restype;
- itype = ((TargetEntry *) lfirst(next_target))->resdom->restype;
-
- /* one or both is a NULL column? then don't convert... */
- if (otype == InvalidOid)
- {
- /* propagate a known type forward, if available */
- if (itype != InvalidOid)
- ((TargetEntry *) lfirst(prev_target))->resdom->restype = itype;
-#ifdef NOT_USED
- else
- {
- ((TargetEntry *) lfirst(prev_target))->resdom->restype = UNKNOWNOID;
- ((TargetEntry *) lfirst(next_target))->resdom->restype = UNKNOWNOID;
- }
-#endif
- }
- else if (itype == InvalidOid)
- {
- }
- /* they don't match in type? then convert... */
- else if (itype != otype)
- {
- Node *expr;
-
- expr = ((TargetEntry *) lfirst(next_target))->expr;
- expr = CoerceTargetExpr(NULL, expr, itype, otype);
- if (expr == NULL)
- {
- elog(ERROR, "Unable to transform %s to %s"
- "\n\tEach UNION | EXCEPT | INTERSECT clause must have compatible target types",
- typeidTypeName(itype),
- typeidTypeName(otype));
- }
- ((TargetEntry *) lfirst(next_target))->expr = expr;
- ((TargetEntry *) lfirst(next_target))->resdom->restype = otype;
- }
-
- /* both are UNKNOWN? then evaluate as text... */
- else if (itype == UNKNOWNOID)
+ foreach(l, querylist)
{
- ((TargetEntry *) lfirst(next_target))->resdom->restype = TEXTOID;
- ((TargetEntry *) lfirst(prev_target))->resdom->restype = TEXTOID;
- }
- prev_target = lnext(prev_target);
- }
-}
+ Query *query = (Query *) lfirst(l);
-/***S*I***/
-/* Rewrites UNION INTERSECT and EXCEPT queries to semantiacally equivalent
- * queries that use IN and NOT IN subselects.
- *
- * The operator tree is attached to 'intersectClause' (see rule
- * 'SelectStmt' in gram.y) of the 'parsetree' given as an
- * argument. First we remember some clauses (the sortClause, the
- * unique flag etc.) Then we translate the operator tree to DNF
- * (disjunctive normal form) by 'cnfify'. (Note that 'cnfify' produces
- * CNF but as we exchanged ANDs with ORs in function A_Expr_to_Expr()
- * earlier we get DNF after exchanging ANDs and ORs again in the
- * result.) Now we create a new query by evaluating the new operator
- * tree which is in DNF now. For every AND we create an entry in the
- * union list and for every OR we create an IN subselect. (NOT IN
- * subselects are created for OR NOT nodes). The first entry of the
- * union list is handed back but before that the remembered clauses
- * (sortClause etc) are attached to the new top Node (Note that the
- * new top Node can differ from the parsetree given as argument because of
- * the translation to DNF. That's why we have to remember the sortClause or
- * unique flag!) */
-Query *
-Except_Intersect_Rewrite (Query *parsetree)
-{
-
- SubLink *n;
- Query *result, *intersect_node;
- List *elist, *intersect_list = NIL, *intersect, *intersectClause;
- List *union_list = NIL, *sortClause;
- List *left_expr, *right_expr, *resnames = NIL;
- char *op, *uniqueFlag, *into;
- bool isBinary, isPortal, isTemp;
- CmdType commandType = CMD_SELECT;
- List *rtable_insert = NIL;
-
- List *prev_target = NIL;
-
- /* Remember the Resnames of the given parsetree's targetlist
- * (these are the resnames of the first Select Statement of
- * the query formulated by the user and he wants the columns
- * named by these strings. The transformation to DNF can
- * cause another Select Statment to be the top one which
- * uses other names for its columns. Therefore we remeber
- * the original names and attach them to the targetlist
- * of the new topmost Node at the end of this function */
- foreach(elist, parsetree->targetList)
- {
- TargetEntry *tent = (TargetEntry *)lfirst(elist);
-
- resnames = lappend(resnames, tent->resdom->resname);
- }
-
- /* If the Statement is an INSERT INTO ... (SELECT...) statement
- * using UNIONs, INTERSECTs or EXCEPTs and the transformation
- * to DNF makes another Node to the top node we have to transform
- * the new top node to an INSERT node and the original INSERT node
- * to a SELECT node */
- if (parsetree->commandType == CMD_INSERT)
- {
- parsetree->commandType = CMD_SELECT;
- commandType = CMD_INSERT;
- parsetree->resultRelation = 0;
-
- /* The result relation ( = the one to insert into) has to be
- * attached to the rtable list of the new top node */
- rtable_insert = nth(length(parsetree->rtable) - 1, parsetree->rtable);
- }
-
- /* Save some items, to be able to attach them to the resulting top node
- * at the end of the function */
- sortClause = parsetree->sortClause;
- uniqueFlag = parsetree->uniqueFlag;
- into = parsetree->into;
- isBinary = parsetree->isBinary;
- isPortal = parsetree->isPortal;
- isTemp = parsetree->isTemp;
-
- /* The operator tree attached to parsetree->intersectClause is still 'raw'
- * ( = the leaf nodes are still SelectStmt nodes instead of Query nodes)
- * So step through the tree and transform the nodes using parse_analyze().
- *
- * The parsetree (given as an argument to
- * Except_Intersect_Rewrite()) has already been transformed and
- * transforming it again would cause troubles. So we give the 'raw'
- * version (of the cooked parsetree) to the function to
- * prevent an additional transformation. Instead we hand back the
- * 'cooked' version also given as an argument to
- * intersect_tree_analyze() */
- intersectClause =
- (List *)intersect_tree_analyze((Node *)parsetree->intersectClause,
- (Node *)lfirst(parsetree->unionClause),
- (Node *)parsetree);
-
- /* intersectClause is no longer needed so set it to NIL */
- parsetree->intersectClause = NIL;
- /* unionClause will be needed later on but the list it delivered
- * is no longer needed, so set it to NIL */
- parsetree->unionClause = NIL;
-
- /* Transform the operator tree to DNF (remember ANDs and ORs have been exchanged,
- * that's why we get DNF by using cnfify)
- *
- * After the call, explicit ANDs are removed and all AND operands
- * are simply items in the intersectClause list */
- intersectClause = cnfify((Expr *)intersectClause, true);
-
- /* For every entry of the intersectClause list we generate one entry in
- * the union_list */
- foreach(intersect, intersectClause)
- {
- /* for every OR we create an IN subselect and for every OR NOT
- * we create a NOT IN subselect, so first extract all the Select
- * Query nodes from the tree (that contains only OR or OR NOTs
- * any more because we did a transformation to DNF
- *
- * There must be at least one node that is not negated
- * (i.e. just OR and not OR NOT) and this node will be the first
- * in the list returned */
- intersect_list = NIL;
- create_list((Node *)lfirst(intersect), &intersect_list);
-
- /* This one will become the Select Query node, all other
- * nodes are transformed into subselects under this node! */
- intersect_node = (Query *)lfirst(intersect_list);
- intersect_list = lnext(intersect_list);
-
- /* Check if all Select Statements use the same number of attributes and
- * if all corresponding attributes are of the same type */
- if (prev_target)
- check_targetlists_are_compatible(prev_target, intersect_node->targetList);
- prev_target = intersect_node->targetList;
- /* End of check for corresponding targetlists */
-
- /* Transform all nodes remaining into subselects and add them to
- * the qualifications of the Select Query node */
- while(intersect_list != NIL) {
-
- n = makeNode(SubLink);
-
- /* Here we got an OR so transform it to an IN subselect */
- if(IsA(lfirst(intersect_list), Query))
- {
- /* Check if all Select Statements use the same number of attributes and
- * if all corresponding attributes are of the same type */
- check_targetlists_are_compatible(prev_target,
- ((Query *)lfirst(intersect_list))->targetList);
- /* End of check for corresponding targetlists */
-
- n->subselect = lfirst(intersect_list);
- op = "=";
- n->subLinkType = ANY_SUBLINK;
- n->useor = false;
- }
- /* Here we got an OR NOT node so transform it to a NOT IN subselect */
- else
- {
- /* Check if all Select Statements use the same number of attributes and
- * if all corresponding attributes are of the same type */
- check_targetlists_are_compatible(prev_target,
- ((Query *)lfirst(((Expr *)lfirst(intersect_list))->args))->targetList);
- /* End of check for corresponding targetlists */
-
- n->subselect = (Node *)lfirst(((Expr *)lfirst(intersect_list))->args);
- op = "<>";
- n->subLinkType = ALL_SUBLINK;
- n->useor = true;
- }
-
- /* Prepare the lefthand side of the Sublinks: All the entries of the
- * targetlist must be (IN) or must not be (NOT IN) the subselect */
- foreach(elist, intersect_node->targetList)
- {
- Node *expr = lfirst(elist);
- TargetEntry *tent = (TargetEntry *)expr;
-
- n->lefthand = lappend(n->lefthand, tent->expr);
- }
-
- /* The first arguments of oper also have to be created for the
- * sublink (they are the same as the lefthand side!) */
- left_expr = n->lefthand;
- right_expr = ((Query *)(n->subselect))->targetList;
-
- foreach(elist, left_expr)
- {
- Node *lexpr = lfirst(elist);
- Node *rexpr = lfirst(right_expr);
- TargetEntry *tent = (TargetEntry *) rexpr;
- Expr *op_expr;
-
- op_expr = make_op(op, lexpr, tent->expr);
-
- n->oper = lappend(n->oper, op_expr);
- right_expr = lnext(right_expr);
- }
-
- /* If the Select Query node has aggregates in use
- * add all the subselects to the HAVING qual else to
- * the WHERE qual */
- if(intersect_node->hasAggs == false) {
- AddQual(intersect_node, (Node *)n);
- }
- else {
- AddHavingQual(intersect_node, (Node *)n);
- }
-
- /* Now we got sublinks */
- intersect_node->hasSubLinks = true;
- intersect_list = lnext(intersect_list);
- }
- intersect_node->intersectClause = NIL;
- union_list = lappend(union_list, intersect_node);
- }
-
- /* The first entry to union_list is our new top node */
- result = (Query *)lfirst(union_list);
- /* attach the rest to unionClause */
- result->unionClause = lnext(union_list);
- /* Attach all the items remembered in the beginning of the function */
- result->sortClause = sortClause;
- result->uniqueFlag = uniqueFlag;
- result->into = into;
- result->isPortal = isPortal;
- result->isBinary = isBinary;
- result->isTemp = isTemp;
-
- /* The relation to insert into is attached to the range table
- * of the new top node */
- if (commandType == CMD_INSERT)
- {
- result->rtable = lappend(result->rtable, rtable_insert);
- result->resultRelation = length(result->rtable);
- result->commandType = commandType;
- }
- /* The resnames of the originally first SelectStatement are
- * attached to the new first SelectStatement */
- foreach(elist, result->targetList)
- {
- TargetEntry *tent = (TargetEntry *)lfirst(elist);
-
- tent->resdom->resname = lfirst(resnames);
- resnames = lnext(resnames);
- }
- return result;
-}
+ 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);
-/* Create a list of nodes that are either Query nodes of NOT Expr
- * nodes followed by a Query node. The tree given in ptr contains at
- * least one non negated Query node. This node is attached to the
- * beginning of the list */
+ if (rte->rtekind == RTE_SUBQUERY)
+ {
+ switch (query->commandType)
+ {
+ case CMD_INSERT:
+ elog(ERROR, "Cannot insert into a view"
+ "\n\tYou need an unconditional ON INSERT DO INSTEAD rule");
+ break;
+ case CMD_UPDATE:
+ elog(ERROR, "Cannot update a view"
+ "\n\tYou need an unconditional ON UPDATE DO INSTEAD rule");
+ break;
+ case CMD_DELETE:
+ elog(ERROR, "Cannot delete from a view"
+ "\n\tYou need an unconditional ON DELETE DO INSTEAD rule");
+ break;
+ default:
+ elog(ERROR, "QueryRewrite: unexpected commandType %d",
+ (int) query->commandType);
+ break;
+ }
+ }
+ }
-void create_list(Node *ptr, List **intersect_list)
-{
- List *arg;
-
- if(IsA(ptr,Query))
- {
- /* The non negated node is attached at the beginning (lcons) */
- *intersect_list = lcons(ptr, *intersect_list);
- return;
- }
-
- if(IsA(ptr,Expr))
- {
- if(((Expr *)ptr)->opType == NOT_EXPR)
- {
- /* negated nodes are appended to the end (lappend) */
- *intersect_list = lappend(*intersect_list, ptr);
- return;
+ results = lappend(results, query);
}
- else
- {
- foreach(arg, ((Expr *)ptr)->args)
- {
- create_list(lfirst(arg), intersect_list);
- }
- return;
- }
- return;
- }
-}
-/* The nodes given in 'tree' are still 'raw' so 'cook' them using parse_analyze().
- * The node given in first_select has already been cooked, so don't transform
- * it again but return a pointer to the previously cooked version given in 'parsetree'
- * instead. */
-Node *intersect_tree_analyze(Node *tree, Node *first_select, Node *parsetree)
-{
- Node *result = (Node *)NIL;
- List *arg;
-
- if(IsA(tree, SelectStmt))
- {
- QueryTreeList *qtree;
-
- /* If we get to the tree given in first_select return
- * parsetree instead of performing parse_analyze() */
- if(tree == first_select){
- result = parsetree;
- }
- else {
- /* transform the 'raw' nodes to 'cooked' Query nodes */
- qtree = parse_analyze(lcons(tree, NIL), NULL);
- result = (Node *)qtree->qtrees[0];
- }
-
- }
- if(IsA(tree,Expr))
- {
- /* Call recursively for every argument of the node */
- foreach(arg, ((Expr *)tree)->args)
- {
- lfirst(arg) = intersect_tree_analyze(lfirst(arg), first_select, parsetree);
- }
- result = tree;
- }
- return result;
-}
+ /*
+ * Step 3
+ *
+ * Determine which, if any, of the resulting queries is supposed to set
+ * 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.)
+ *
+ * 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.
+ */
+ origCmdType = parsetree->commandType;
+ foundOriginalQuery = false;
+ lastInstead = NULL;
+ foreach(l, results)
+ {
+ Query *query = (Query *) lfirst(l);
+ if (query->querySource == QSRC_ORIGINAL)
+ {
+ Assert(query->canSetTag);
+ Assert(!foundOriginalQuery);
+ foundOriginalQuery = true;
+#ifndef USE_ASSERT_CHECKING
+ break;
+#endif
+ }
+ else
+ {
+ Assert(!query->canSetTag);
+ if (query->commandType == origCmdType &&
+ (query->querySource == QSRC_INSTEAD_RULE ||
+ query->querySource == QSRC_QUAL_INSTEAD_RULE))
+ lastInstead = query;
+ }
+ }
+ if (!foundOriginalQuery && lastInstead != NULL)
+ lastInstead->canSetTag = true;
+ return results;
+}