mirror of
https://github.com/postgres/postgres.git
synced 2025-04-25 21:42:33 +03:00
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.
This commit is contained in:
parent
aedd189a5c
commit
84666801ed
@ -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 @@
|
|||||||
#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,
|
|||||||
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,
|
|||||||
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,
|
|||||||
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,
|
|||||||
*/
|
*/
|
||||||
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)
|
|||||||
* 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)
|
|||||||
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)
|
|||||||
* 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)
|
|||||||
* 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)
|
|||||||
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)
|
|||||||
*/
|
*/
|
||||||
if (rte->rtekind == RTE_SUBQUERY)
|
if (rte->rtekind == RTE_SUBQUERY)
|
||||||
{
|
{
|
||||||
rte->subquery = fireRIRrules(rte->subquery);
|
rte->subquery = fireRIRrules(rte->subquery, activeRIRs);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -814,8 +821,18 @@ fireRIRrules(Query *parsetree)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Now apply them
|
* If we found any, apply them --- but first check for recursion!
|
||||||
*/
|
*/
|
||||||
|
if (locks != NIL)
|
||||||
|
{
|
||||||
|
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)
|
foreach(l, locks)
|
||||||
{
|
{
|
||||||
rule = lfirst(l);
|
rule = lfirst(l);
|
||||||
@ -825,7 +842,9 @@ fireRIRrules(Query *parsetree)
|
|||||||
rt_index,
|
rt_index,
|
||||||
rule->attrno == -1,
|
rule->attrno == -1,
|
||||||
rel,
|
rel,
|
||||||
relIsUsed);
|
relIsUsed,
|
||||||
|
newActiveRIRs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
heap_close(rel, NoLock);
|
heap_close(rel, NoLock);
|
||||||
@ -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,
|
|||||||
* 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,41 +1025,34 @@ 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;
|
||||||
|
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. Also, utilities aren't rewritten at all
|
||||||
|
* (do we still need that check?)
|
||||||
|
*/
|
||||||
|
if (event != CMD_SELECT && event != CMD_UTILITY)
|
||||||
|
{
|
||||||
int result_relation;
|
int result_relation;
|
||||||
RangeTblEntry *rt_entry;
|
RangeTblEntry *rt_entry;
|
||||||
Relation rt_entry_relation;
|
Relation rt_entry_relation;
|
||||||
RuleLock *rt_entry_locks;
|
List *locks;
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* the statement is an update, insert or delete - fire rules on it.
|
|
||||||
*/
|
|
||||||
result_relation = parsetree->resultRelation;
|
result_relation = parsetree->resultRelation;
|
||||||
Assert(result_relation != 0);
|
Assert(result_relation != 0);
|
||||||
rt_entry = rt_fetch(result_relation, parsetree->rtable);
|
rt_entry = rt_fetch(result_relation, parsetree->rtable);
|
||||||
@ -1067,63 +1079,55 @@ RewriteQuery(Query *parsetree, bool *instead_flag, Query **qual_product)
|
|||||||
/*
|
/*
|
||||||
* Collect and apply the appropriate rules.
|
* Collect and apply the appropriate rules.
|
||||||
*/
|
*/
|
||||||
rt_entry_locks = rt_entry_relation->rd_rules;
|
locks = matchLocks(event, rt_entry_relation->rd_rules,
|
||||||
|
|
||||||
if (rt_entry_locks != NULL)
|
|
||||||
{
|
|
||||||
List *locks = matchLocks(event, rt_entry_locks,
|
|
||||||
result_relation, parsetree);
|
result_relation, parsetree);
|
||||||
|
|
||||||
|
if (locks != NIL)
|
||||||
|
{
|
||||||
|
List *product_queries;
|
||||||
|
|
||||||
product_queries = fireRules(parsetree,
|
product_queries = fireRules(parsetree,
|
||||||
result_relation,
|
result_relation,
|
||||||
event,
|
event,
|
||||||
locks,
|
locks,
|
||||||
instead_flag,
|
&instead,
|
||||||
qual_product);
|
&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));
|
||||||
|
}
|
||||||
|
|
||||||
|
rev = (rewrite_event *) palloc(sizeof(rewrite_event));
|
||||||
|
rev->relation = RelationGetRelid(rt_entry_relation);
|
||||||
|
rev->event = event;
|
||||||
|
rewrite_events = lcons(rev, rewrite_events);
|
||||||
|
|
||||||
|
foreach(n, product_queries)
|
||||||
|
{
|
||||||
|
Query *pt = (Query *) lfirst(n);
|
||||||
|
List *newstuff;
|
||||||
|
|
||||||
|
newstuff = RewriteQuery(pt, rewrite_events);
|
||||||
|
rewritten = nconc(rewritten, newstuff);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
heap_close(rt_entry_relation, NoLock); /* keep lock! */
|
heap_close(rt_entry_relation, NoLock); /* keep lock! */
|
||||||
|
|
||||||
return 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 100
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static int numQueryRewriteInvoked = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* deepRewriteQuery -
|
|
||||||
* rewrites the query and apply the rules again on the queries rewritten
|
|
||||||
*/
|
|
||||||
static List *
|
|
||||||
deepRewriteQuery(Query *parsetree)
|
|
||||||
{
|
|
||||||
List *rewritten = NIL;
|
|
||||||
List *result;
|
|
||||||
bool instead = false;
|
|
||||||
Query *qual_product = NULL;
|
|
||||||
List *n;
|
|
||||||
|
|
||||||
if (++numQueryRewriteInvoked > REWRITE_INVOKE_MAX)
|
|
||||||
elog(ERROR, "query rewritten %d times, may contain cycles",
|
|
||||||
numQueryRewriteInvoked - 1);
|
|
||||||
|
|
||||||
result = RewriteQuery(parsetree, &instead, &qual_product);
|
|
||||||
|
|
||||||
foreach(n, result)
|
|
||||||
{
|
|
||||||
Query *pt = lfirst(n);
|
|
||||||
List *newstuff;
|
|
||||||
|
|
||||||
newstuff = deepRewriteQuery(pt);
|
|
||||||
rewritten = nconc(rewritten, newstuff);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -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)
|
|||||||
*
|
*
|
||||||
* 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)
|
|||||||
{
|
{
|
||||||
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.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user