Commit 84666801 authored by Tom Lane's avatar Tom Lane

Remove REWRITE_INVOKE_MAX in favor of making an accurate check for

recursion in RewriteQuery(); also, detect recursion in fireRIRrules(),
so as to catch self-referential views per example from Ryan VanderBijl.
Minor code restructuring to make it easier to catch recursive case.
parent aedd189a
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.117 2003/02/13 21:39:50 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.118 2003/02/25 23:47:43 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -33,6 +33,12 @@ ...@@ -33,6 +33,12 @@
#include "utils/lsyscache.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, static Query *rewriteRuleAction(Query *parsetree,
Query *rule_action, Query *rule_action,
Node *rule_qual, Node *rule_qual,
...@@ -45,7 +51,7 @@ static TargetEntry *process_matched_tle(TargetEntry *src_tle, ...@@ -45,7 +51,7 @@ static TargetEntry *process_matched_tle(TargetEntry *src_tle,
static void markQueryForUpdate(Query *qry, bool skipOldNew); static void markQueryForUpdate(Query *qry, bool skipOldNew);
static List *matchLocks(CmdType event, RuleLock *rulelocks, static List *matchLocks(CmdType event, RuleLock *rulelocks,
int varno, Query *parsetree); int varno, Query *parsetree);
static Query *fireRIRrules(Query *parsetree); static Query *fireRIRrules(Query *parsetree, List *activeRIRs);
/* /*
...@@ -526,8 +532,8 @@ matchLocks(CmdType event, ...@@ -526,8 +532,8 @@ matchLocks(CmdType event,
int nlocks; int nlocks;
int i; int i;
Assert(rulelocks != NULL); /* we get called iff there is some lock */ if (rulelocks == NULL)
Assert(parsetree != NULL); return NIL;
if (parsetree->commandType != CMD_SELECT) if (parsetree->commandType != CMD_SELECT)
{ {
...@@ -562,7 +568,8 @@ ApplyRetrieveRule(Query *parsetree, ...@@ -562,7 +568,8 @@ ApplyRetrieveRule(Query *parsetree,
int rt_index, int rt_index,
bool relation_level, bool relation_level,
Relation relation, Relation relation,
bool relIsUsed) bool relIsUsed,
List *activeRIRs)
{ {
Query *rule_action; Query *rule_action;
RangeTblEntry *rte, RangeTblEntry *rte,
...@@ -581,7 +588,7 @@ ApplyRetrieveRule(Query *parsetree, ...@@ -581,7 +588,7 @@ ApplyRetrieveRule(Query *parsetree,
*/ */
rule_action = copyObject(lfirst(rule->actions)); 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 * VIEWs are really easy --- just plug the view query in as a
...@@ -683,7 +690,7 @@ markQueryForUpdate(Query *qry, bool skipOldNew) ...@@ -683,7 +690,7 @@ markQueryForUpdate(Query *qry, bool skipOldNew)
* the SubLink's subselect link with the possibly-rewritten subquery. * the SubLink's subselect link with the possibly-rewritten subquery.
*/ */
static bool static bool
fireRIRonSubLink(Node *node, void *context) fireRIRonSubLink(Node *node, List *activeRIRs)
{ {
if (node == NULL) if (node == NULL)
return false; return false;
...@@ -692,7 +699,8 @@ fireRIRonSubLink(Node *node, void *context) ...@@ -692,7 +699,8 @@ fireRIRonSubLink(Node *node, void *context)
SubLink *sub = (SubLink *) node; SubLink *sub = (SubLink *) node;
/* Do what we came for */ /* 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 */ /* Fall through to process lefthand args of SubLink */
} }
...@@ -701,7 +709,7 @@ fireRIRonSubLink(Node *node, void *context) ...@@ -701,7 +709,7 @@ fireRIRonSubLink(Node *node, void *context)
* processed subselects of subselects for us. * processed subselects of subselects for us.
*/ */
return expression_tree_walker(node, fireRIRonSubLink, return expression_tree_walker(node, fireRIRonSubLink,
(void *) context); (void *) activeRIRs);
} }
...@@ -710,7 +718,7 @@ fireRIRonSubLink(Node *node, void *context) ...@@ -710,7 +718,7 @@ fireRIRonSubLink(Node *node, void *context)
* Apply all RIR rules on each rangetable entry in a query * Apply all RIR rules on each rangetable entry in a query
*/ */
static Query * static Query *
fireRIRrules(Query *parsetree) fireRIRrules(Query *parsetree, List *activeRIRs)
{ {
int rt_index; int rt_index;
...@@ -729,7 +737,6 @@ fireRIRrules(Query *parsetree) ...@@ -729,7 +737,6 @@ fireRIRrules(Query *parsetree)
LOCKMODE lockmode; LOCKMODE lockmode;
bool relIsUsed; bool relIsUsed;
int i; int i;
List *l;
++rt_index; ++rt_index;
...@@ -742,7 +749,7 @@ fireRIRrules(Query *parsetree) ...@@ -742,7 +749,7 @@ fireRIRrules(Query *parsetree)
*/ */
if (rte->rtekind == RTE_SUBQUERY) if (rte->rtekind == RTE_SUBQUERY)
{ {
rte->subquery = fireRIRrules(rte->subquery); rte->subquery = fireRIRrules(rte->subquery, activeRIRs);
continue; continue;
} }
...@@ -814,18 +821,30 @@ fireRIRrules(Query *parsetree) ...@@ -814,18 +821,30 @@ fireRIRrules(Query *parsetree)
} }
/* /*
* Now apply them * If we found any, apply them --- but first check for recursion!
*/ */
foreach(l, locks) if (locks != NIL)
{ {
rule = lfirst(l); List *newActiveRIRs;
List *l;
parsetree = ApplyRetrieveRule(parsetree,
rule, if (oidMember(RelationGetRelid(rel), activeRIRs))
rt_index, elog(ERROR, "Infinite recursion detected in rules for relation %s",
rule->attrno == -1, RelationGetRelationName(rel));
rel, newActiveRIRs = lconso(RelationGetRelid(rel), activeRIRs);
relIsUsed);
foreach(l, locks)
{
rule = lfirst(l);
parsetree = ApplyRetrieveRule(parsetree,
rule,
rt_index,
rule->attrno == -1,
rel,
relIsUsed,
newActiveRIRs);
}
} }
heap_close(rel, NoLock); heap_close(rel, NoLock);
...@@ -836,7 +855,7 @@ fireRIRrules(Query *parsetree) ...@@ -836,7 +855,7 @@ fireRIRrules(Query *parsetree)
* in the rtable. * in the rtable.
*/ */
if (parsetree->hasSubLinks) if (parsetree->hasSubLinks)
query_tree_walker(parsetree, fireRIRonSubLink, NULL, query_tree_walker(parsetree, fireRIRonSubLink, (void *) activeRIRs,
QTW_IGNORE_RT_SUBQUERIES); QTW_IGNORE_RT_SUBQUERIES);
/* /*
...@@ -967,7 +986,7 @@ fireRules(Query *parsetree, ...@@ -967,7 +986,7 @@ fireRules(Query *parsetree,
* its actions only in cases where the rule quals of all * its actions only in cases where the rule quals of all
* INSTEAD rules are false. Think of it as the default action * INSTEAD rules are false. Think of it as the default action
* in a case. We save this in *qual_product so * in a case. We save this in *qual_product so
* deepRewriteQuery() can add it to the query list after we * RewriteQuery() can add it to the query list after we
* mangled it up enough. * mangled it up enough.
* *
* If we have already found an unqualified INSTEAD rule, * If we have already found an unqualified INSTEAD rule,
...@@ -1006,124 +1025,109 @@ fireRules(Query *parsetree, ...@@ -1006,124 +1025,109 @@ fireRules(Query *parsetree,
/* /*
* One pass of rewriting a single query. * RewriteQuery -
* rewrites the query and apply the rules again on the queries rewritten
* *
* parsetree is the input query. Return value and output parameters * rewrite_events is a list of open query-rewrite actions, so we can detect
* are defined the same as for fireRules, above. * infinite recursion.
*/ */
static List * static List *
RewriteQuery(Query *parsetree, bool *instead_flag, Query **qual_product) RewriteQuery(Query *parsetree, List *rewrite_events)
{ {
CmdType event; CmdType event = parsetree->commandType;
List *product_queries = NIL; bool instead = false;
int result_relation; Query *qual_product = NULL;
RangeTblEntry *rt_entry; List *rewritten = NIL;
Relation rt_entry_relation;
RuleLock *rt_entry_locks;
Assert(parsetree != NULL);
event = parsetree->commandType;
/* /*
* 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 * SELECT rules are handled later when we have all the queries that
* should get executed * should get executed. Also, utilities aren't rewritten at all
*/ * (do we still need that check?)
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.
*/
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) if (event != CMD_SELECT && event != CMD_UTILITY)
rewriteTargetList(parsetree, rt_entry_relation); {
int result_relation;
RangeTblEntry *rt_entry;
Relation rt_entry_relation;
List *locks;
/* result_relation = parsetree->resultRelation;
* Collect and apply the appropriate rules. Assert(result_relation != 0);
*/ rt_entry = rt_fetch(result_relation, parsetree->rtable);
rt_entry_locks = rt_entry_relation->rd_rules; Assert(rt_entry->rtekind == RTE_RELATION);
if (rt_entry_locks != NULL) /*
{ * This may well be the first access to the result relation during the
List *locks = matchLocks(event, rt_entry_locks, * current statement (it will be, if this Query was extracted from a
result_relation, parsetree); * rule or somehow got here other than via the parser). Therefore,
* grab the appropriate lock type for a result relation, and do not
product_queries = fireRules(parsetree, * release it until end of transaction. This protects the rewriter
result_relation, * and planner against schema changes mid-query.
event, */
locks, rt_entry_relation = heap_open(rt_entry->relid, RowExclusiveLock);
instead_flag,
qual_product);
}
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;
/* product_queries = fireRules(parsetree,
* to avoid infinite recursion, we restrict the number of times a query result_relation,
* can be rewritten. Detecting cycles is left for the reader as an exercise. event,
*/ locks,
#ifndef REWRITE_INVOKE_MAX &instead,
#define REWRITE_INVOKE_MAX 100 &qual_product);
#endif
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;
/* foreach(n, rewrite_events)
* deepRewriteQuery - {
* rewrites the query and apply the rules again on the queries rewritten rev = (rewrite_event *) lfirst(n);
*/ if (rev->relation == RelationGetRelid(rt_entry_relation) &&
static List * rev->event == event)
deepRewriteQuery(Query *parsetree) elog(ERROR, "Infinite recursion detected in rules for relation %s",
{ RelationGetRelationName(rt_entry_relation));
List *rewritten = NIL; }
List *result;
bool instead = false;
Query *qual_product = NULL;
List *n;
if (++numQueryRewriteInvoked > REWRITE_INVOKE_MAX) rev = (rewrite_event *) palloc(sizeof(rewrite_event));
elog(ERROR, "query rewritten %d times, may contain cycles", rev->relation = RelationGetRelid(rt_entry_relation);
numQueryRewriteInvoked - 1); rev->event = event;
rewrite_events = lcons(rev, rewrite_events);
result = RewriteQuery(parsetree, &instead, &qual_product); foreach(n, product_queries)
{
Query *pt = (Query *) lfirst(n);
List *newstuff;
foreach(n, result) newstuff = RewriteQuery(pt, rewrite_events);
{ rewritten = nconc(rewritten, newstuff);
Query *pt = lfirst(n); }
List *newstuff; }
}
newstuff = deepRewriteQuery(pt); heap_close(rt_entry_relation, NoLock); /* keep lock! */
rewritten = nconc(rewritten, newstuff);
} }
/* /*
...@@ -1161,22 +1165,6 @@ deepRewriteQuery(Query *parsetree) ...@@ -1161,22 +1165,6 @@ deepRewriteQuery(Query *parsetree)
} }
/*
* 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 - * QueryRewrite -
* Primary entry point to the query rewriter. * Primary entry point to the query rewriter.
...@@ -1198,7 +1186,7 @@ QueryRewrite(Query *parsetree) ...@@ -1198,7 +1186,7 @@ QueryRewrite(Query *parsetree)
* *
* Apply all non-SELECT rules possibly getting 0 or many queries * Apply all non-SELECT rules possibly getting 0 or many queries
*/ */
querylist = QueryRewriteOne(parsetree); querylist = RewriteQuery(parsetree, NIL);
/* /*
* Step 2 * Step 2
...@@ -1209,7 +1197,7 @@ QueryRewrite(Query *parsetree) ...@@ -1209,7 +1197,7 @@ QueryRewrite(Query *parsetree)
{ {
Query *query = (Query *) lfirst(l); Query *query = (Query *) lfirst(l);
query = fireRIRrules(query); query = fireRIRrules(query, NIL);
/* /*
* If the query target was rewritten as a view, complain. * If the query target was rewritten as a view, complain.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment