* rewriteHandler.c
* Primary module of query rewriter.
*
- * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
+ * 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.101 2002/04/05 05:47:05 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.122 2003/07/03 16:34:25 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "parser/parse_coerce.h"
#include "parser/parse_expr.h"
#include "parser/parse_oper.h"
-#include "parser/parse_target.h"
#include "parser/parse_type.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteHandler.h"
#include "utils/lsyscache.h"
+/* 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,
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 Node *build_column_default(Relation rel, int attrno);
+ 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);
+static Query *fireRIRrules(Query *parsetree, List *activeRIRs);
/*
{
int current_varno,
new_varno;
+ List *main_rtable;
int rt_length;
Query *sub_action;
Query **sub_action_ptr;
+ List *rt;
/*
* Make modifiable copies of rule action and qual (what we're passed
* 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 clean those out we'd have a much
+ * 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.
*/
- sub_action->rtable = nconc((List *) copyObject(parsetree->rtable),
- sub_action->rtable);
+ main_rtable = (List *) copyObject(parsetree->rtable);
+
+ foreach(rt, main_rtable)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
+
+ rte->checkForWrite = false;
+ }
+
+ sub_action->rtable = nconc(main_rtable, sub_action->rtable);
/*
* Each rule action's jointree should be the main parsetree's jointree
{
RangeTblRef *rtr = lfirst(jjt);
- if (IsA(rtr, RangeTblRef) &&rtr->rtindex == rt_index)
+ if (IsA(rtr, RangeTblRef) &&
+ rtr->rtindex == rt_index)
{
newjointree = lremove(rtr, newjointree);
+ /* foreach is safe because we exit loop after lremove... */
break;
}
}
* 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.)
+ * 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
* 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
+ * 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.
*/
for (attrno = 1; attrno <= numattrs; attrno++)
{
- Form_pg_attribute att_tup = target_relation->rd_att->attrs[attrno-1];
+ Form_pg_attribute att_tup = target_relation->rd_att->attrs[attrno - 1];
TargetEntry *new_tle = NULL;
+ /* We can ignore deleted attributes */
+ if (att_tup->attisdropped)
+ continue;
+
/*
- * Look for targetlist entries matching this attr. We match by
+ * 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.
}
}
- if (new_tle == NULL && commandType == CMD_INSERT)
+ /*
+ * 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)))
{
- /*
- * Didn't find a matching tlist entry; if it's an INSERT,
- * look for a default value, and add a tlist entry computing
- * the default if we find one.
- */
Node *new_expr;
new_expr = build_column_default(target_relation, attrno);
+ /*
+ * If there is no default (ie, default is effectively NULL),
+ * we can omit the tlist entry in the INSERT case, since the
+ * planner can insert a NULL for itself, and there's no point
+ * in spending any more rewriter cycles on the entry. But in the
+ * UPDATE case we've got to explicitly set the column to NULL.
+ */
+ if (!new_expr)
+ {
+ if (commandType == CMD_INSERT)
+ new_tle = NULL;
+ else
+ {
+ 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);
+ }
+ }
+
if (new_expr)
new_tle = makeTargetEntry(makeResdom(attrno,
att_tup->atttypid,
att_tup->atttypmod,
- pstrdup(NameStr(att_tup->attname)),
+ pstrdup(NameStr(att_tup->attname)),
false),
- new_expr);
+ (Expr *) new_expr);
}
if (new_tle)
((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)->refelemtype !=
- ((ArrayRef *) prior_tle->expr)->refelemtype)
+ ((ArrayRef *) src_tle->expr)->refrestype !=
+ ((ArrayRef *) prior_tle->expr)->refrestype)
elog(ERROR, "Multiple assignments to same attribute \"%s\"",
resdom->resname);
* Prior TLE could be a nest of ArrayRefs if we do this more than
* once.
*/
- priorbottom = ((ArrayRef *) prior_tle->expr)->refexpr;
+ priorbottom = (Node *) ((ArrayRef *) prior_tle->expr)->refexpr;
while (priorbottom != NULL && IsA(priorbottom, ArrayRef) &&
((ArrayRef *) priorbottom)->refassgnexpr != NULL)
- priorbottom = ((ArrayRef *) priorbottom)->refexpr;
+ priorbottom = (Node *) ((ArrayRef *) priorbottom)->refexpr;
if (!equal(priorbottom, ((ArrayRef *) src_tle->expr)->refexpr))
elog(ERROR, "Multiple assignments to same attribute \"%s\"",
resdom->resname);
memcpy(newexpr, src_tle->expr, sizeof(ArrayRef));
newexpr->refexpr = prior_tle->expr;
- return makeTargetEntry(resdom, (Node *) newexpr);
+ return makeTargetEntry(resdom, (Expr *) newexpr);
}
*
* If there is no default, return a NULL instead.
*/
-static Node *
+Node *
build_column_default(Relation rel, int attrno)
{
TupleDesc rd_att = rel->rd_att;
if (expr == NULL)
{
/*
- * No per-column default, so look for a default for the type itself.
+ * No per-column default, so look for a default for the type
+ * itself.
*/
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.
+ * 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
+ * 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);
- if (exprtype != atttype)
- {
- expr = CoerceTargetExpr(NULL, expr, exprtype,
- atttype, atttypmod);
-
- /*
- * 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));
- }
-
+ expr = coerce_to_target_type(NULL, /* no UNKNOWN params here */
+ expr, exprtype,
+ atttype, atttypmod,
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST);
/*
- * If the column is a fixed-length type, it may need a
- * length coercion as well as a type coercion.
+ * This really shouldn't fail; should have checked the default's
+ * type when it was created ...
*/
- expr = coerce_type_typmod(NULL, expr, atttype, atttypmod);
+ 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;
}
int varno,
Query *parsetree)
{
- List *real_locks = NIL;
+ List *matching_locks = NIL;
int nlocks;
int i;
- Assert(rulelocks != NULL); /* we get called iff there is some lock */
- Assert(parsetree != NULL);
+ if (rulelocks == NULL)
+ return NIL;
if (parsetree->commandType != CMD_SELECT)
{
rangeTableEntry_used((Node *) parsetree, varno, 0) :
attribute_used((Node *) parsetree,
varno, oneLock->attrno, 0)))
- real_locks = lappend(real_locks, oneLock);
+ matching_locks = lappend(matching_locks, oneLock);
}
}
- return real_locks;
+ return matching_locks;
}
int rt_index,
bool relation_level,
Relation relation,
- bool relIsUsed)
+ bool relIsUsed,
+ List *activeRIRs)
{
Query *rule_action;
RangeTblEntry *rte,
*/
rule_action = copyObject(lfirst(rule->actions));
- rule_action = fireRIRrules(rule_action);
+ rule_action = fireRIRrules(rule_action, activeRIRs);
/*
* VIEWs are really easy --- just plug the view query in as a
* the SubLink's subselect link with the possibly-rewritten subquery.
*/
static bool
-fireRIRonSubLink(Node *node, void *context)
+fireRIRonSubLink(Node *node, List *activeRIRs)
{
if (node == NULL)
return false;
SubLink *sub = (SubLink *) node;
/* Do what we came for */
- sub->subselect = (Node *) fireRIRrules((Query *) (sub->subselect));
+ sub->subselect = (Node *) fireRIRrules((Query *) sub->subselect,
+ activeRIRs);
/* Fall through to process lefthand args of SubLink */
}
* processed subselects of subselects for us.
*/
return expression_tree_walker(node, fireRIRonSubLink,
- (void *) context);
+ (void *) activeRIRs);
}
* Apply all RIR rules on each rangetable entry in a query
*/
static Query *
-fireRIRrules(Query *parsetree)
+fireRIRrules(Query *parsetree, List *activeRIRs)
{
int rt_index;
LOCKMODE lockmode;
bool relIsUsed;
int i;
- List *l;
++rt_index;
*/
if (rte->rtekind == RTE_SUBQUERY)
{
- rte->subquery = fireRIRrules(rte->subquery);
+ rte->subquery = fireRIRrules(rte->subquery, activeRIRs);
continue;
}
}
/*
- * Now apply them
+ * If we found any, apply them --- but first check for recursion!
*/
- foreach(l, locks)
+ if (locks != NIL)
{
- rule = lfirst(l);
-
- parsetree = ApplyRetrieveRule(parsetree,
- rule,
- rt_index,
- rule->attrno == -1,
- rel,
- relIsUsed);
+ List *newActiveRIRs;
+ List *l;
+
+ 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, NoLock);
}
/*
- * Recurse into sublink subqueries, too.
+ * Recurse into sublink subqueries, too. But we already did the ones
+ * in the rtable.
*/
if (parsetree->hasSubLinks)
- query_tree_walker(parsetree, fireRIRonSubLink, NULL,
- false /* already handled the ones in rtable */ );
+ query_tree_walker(parsetree, fireRIRonSubLink, (void *) activeRIRs,
+ QTW_IGNORE_RT_SUBQUERIES);
/*
* If the query was marked having aggregates, check if this is still
/*
- * idea is to fire regular rules first, then qualified instead
- * rules and unqualified instead rules last. Any lemming is counted for.
- */
-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);
- }
- return nconc(nconc(regular, instead_qualified), instead_rules);
-}
-
-
-/*
- * Modify the given query by adding 'AND NOT rule_qual' to its qualification.
- * This is used to generate suitable "else clauses" for conditional INSTEAD
- * rules.
+ * 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
* of the related entries in the query's own targetlist.
*/
static Query *
-CopyAndAddQual(Query *parsetree,
- 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 = (Node *) copyObject(rule_qual);
event,
rt_index);
/* And attach the fixed qual */
- AddNotQual(new_tree, new_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)
{
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 *event_qual;
- List *actions;
+ Node *event_qual = rule_lock->qual;
+ List *actions = rule_lock->actions;
+ QuerySource qsrc;
List *r;
- /* multiple rule action time */
- *instead_flag = rule_lock->isInstead;
- event_qual = rule_lock->qual;
- actions = rule_lock->actions;
-
- if (event_qual != NULL && *instead_flag)
+ /* Determine correct QuerySource value for actions */
+ if (rule_lock->isInstead)
{
- Query *qual_product;
+ if (event_qual != NULL)
+ qsrc = QSRC_QUAL_INSTEAD_RULE;
+ else
+ {
+ qsrc = QSRC_INSTEAD_RULE;
+ *instead_flag = true; /* report unqualified INSTEAD */
+ }
+ }
+ else
+ qsrc = QSRC_NON_INSTEAD_RULE;
+ if (qsrc == QSRC_QUAL_INSTEAD_RULE)
+ {
/*
- * If there are instead rules with qualifications, the
+ * 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
+ * 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_products so
- * deepRewriteQuery() can add it to the query list after we
+ * 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 *) lfirst(*qual_products);
-
- qual_product = CopyAndAddQual(qual_product,
- event_qual,
- rt_index,
- event);
-
- *qual_products = makeList1(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);
rule_action = rewriteRuleAction(parsetree, rule_action,
event_qual, rt_index, event);
+ rule_action->querySource = qsrc;
+ rule_action->canSetTag = false; /* might change later */
+
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;
- RangeTblEntry *rt_entry;
- Relation rt_entry_relation;
- RuleLock *rt_entry_locks;
-
- Assert(parsetree != NULL);
-
- event = parsetree->commandType;
+ CmdType event = parsetree->commandType;
+ bool instead = false;
+ Query *qual_product = NULL;
+ List *rewritten = NIL;
/*
+ * 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
- */
- if (event == CMD_SELECT)
- return NIL;
-
- /*
- * Utilities aren't rewritten at all - why is this here?
- */
- if (event == CMD_UTILITY)
- return NIL;
-
- /*
- * the statement is an update, insert or delete - fire rules on it.
- */
- 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.
+ * should get executed. Also, utilities aren't rewritten at all
+ * (do we still need that check?)
*/
- rt_entry_relation = heap_open(rt_entry->relid, RowExclusiveLock);
+ if (event != CMD_SELECT && event != CMD_UTILITY)
+ {
+ int result_relation;
+ RangeTblEntry *rt_entry;
+ Relation rt_entry_relation;
+ List *locks;
- /*
- * 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);
+ result_relation = parsetree->resultRelation;
+ Assert(result_relation != 0);
+ rt_entry = rt_fetch(result_relation, parsetree->rtable);
+ Assert(rt_entry->rtekind == RTE_RELATION);
- /*
- * Collect and apply the appropriate rules.
- */
- rt_entry_locks = rt_entry_relation->rd_rules;
-
- if (rt_entry_locks != NULL)
- {
- List *locks = matchLocks(event, rt_entry_locks,
- result_relation, parsetree);
-
- product_queries = fireRules(parsetree,
- result_relation,
- event,
- instead_flag,
- locks,
- qual_products);
- }
+ /*
+ * 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);
- heap_close(rt_entry_relation, NoLock); /* keep lock! */
+ /*
+ * 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);
- return product_queries;
-}
+ /*
+ * Collect and apply the appropriate rules.
+ */
+ locks = matchLocks(event, rt_entry_relation->rd_rules,
+ result_relation, parsetree);
+ if (locks != NIL)
+ {
+ List *product_queries;
-/*
- * to avoid infinite recursion, we restrict the number of times a query
- * can be rewritten. Detecting cycles is left for the reader as an exercise.
- */
-#ifndef REWRITE_INVOKE_MAX
-#define REWRITE_INVOKE_MAX 10
-#endif
+ product_queries = fireRules(parsetree,
+ result_relation,
+ event,
+ locks,
+ &instead,
+ &qual_product);
-static int numQueryRewriteInvoked = 0;
+ /*
+ * If we got any product queries, recursively rewrite them
+ * --- but first check for recursion!
+ */
+ if (product_queries != NIL)
+ {
+ List *n;
+ rewrite_event *rev;
-/*
- * 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;
- bool instead;
- List *qual_products = NIL;
+ 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;
+ 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! */
}
/*
* 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 execution makes the deleted (and maybe the updated) tuples
+ * 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->commandType == CMD_INSERT)
- {
- /*
- * qual_products are the original query with the negated rule
- * qualification of an INSTEAD rule
- */
- if (qual_products != NIL)
- rewritten = nconc(qual_products, rewritten);
-
- /*
- * Add the unmodified original query, if no INSTEAD rule was seen.
- */
- if (!instead)
- rewritten = lcons(parsetree, rewritten);
- }
- else
+ if (!instead)
{
- /*
- * qual_products are the original query with the negated rule
- * qualification of an INSTEAD rule
- */
- if (qual_products != NIL)
- rewritten = nconc(rewritten, qual_products);
-
- /*
- * Add the unmodified original query, if no INSTEAD rule was seen.
- */
- if (!instead)
- rewritten = lappend(rewritten, parsetree);
+ if (parsetree->commandType == CMD_INSERT)
+ {
+ 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);
+ }
}
return rewritten;
}
-/*
- * QueryRewriteOne -
- * rewrite one query
- */
-static List *
-QueryRewriteOne(Query *parsetree)
-{
- numQueryRewriteInvoked = 0;
-
- /*
- * take a deep breath and apply all the rewrite rules - ay
- */
- return deepRewriteQuery(parsetree);
-}
-
-
/*
* QueryRewrite -
* Primary entry point to the query rewriter.
List *querylist;
List *results = NIL;
List *l;
+ CmdType origCmdType;
+ bool foundOriginalQuery;
+ Query *lastInstead;
/*
* Step 1
*
* Apply all non-SELECT rules possibly getting 0 or many queries
*/
- querylist = QueryRewriteOne(parsetree);
+ querylist = RewriteQuery(parsetree, NIL);
/*
* Step 2
{
Query *query = (Query *) lfirst(l);
- query = fireRIRrules(query);
+ query = fireRIRrules(query, NIL);
/*
* If the query target was rewritten as a view, complain.
switch (query->commandType)
{
case CMD_INSERT:
- elog(ERROR, "Cannot insert into a view without an appropriate rule");
+ 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 without an appropriate rule");
+ 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 without an appropriate rule");
+ 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",
results = lappend(results, query);
}
+ /*
+ * Step 3
+ *
+ * Determine which, if any, of the resulting queries is supposed to set
+ * the command-result tag; and update the canSetTag fields accordingly.
+ *
+ * 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;
}