summaryrefslogtreecommitdiff
path: root/src/backend/rewrite/rewriteHandler.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/rewrite/rewriteHandler.c')
-rw-r--r--src/backend/rewrite/rewriteHandler.c252
1 files changed, 214 insertions, 38 deletions
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 73c8dbab12..3c267ccdcd 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.152 2005/05/29 18:34:57 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.153 2005/06/03 23:05:28 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -40,6 +40,7 @@ typedef struct rewrite_event
CmdType event; /* type of rule being fired */
} rewrite_event;
+static bool acquireLocksOnSubLinks(Node *node, void *context);
static Query *rewriteRuleAction(Query *parsetree,
Query *rule_action,
Node *rule_qual,
@@ -58,6 +59,181 @@ static Query *fireRIRrules(Query *parsetree, List *activeRIRs);
/*
+ * AcquireRewriteLocks -
+ * Acquire suitable locks on all the relations mentioned in the Query.
+ * These locks will ensure that the relation schemas don't change under us
+ * while we are rewriting and planning the query.
+ *
+ * A secondary purpose of this routine is to fix up JOIN RTE references to
+ * dropped columns (see details below). Because the RTEs are modified in
+ * place, it is generally appropriate for the caller of this routine to have
+ * first done a copyObject() to make a writable copy of the querytree in the
+ * current memory context.
+ *
+ * This processing can, and for efficiency's sake should, be skipped when the
+ * querytree has just been built by the parser: parse analysis already got
+ * all the same locks we'd get here, and the parser will have omitted dropped
+ * columns from JOINs to begin with. But we must do this whenever we are
+ * dealing with a querytree produced earlier than the current command.
+ *
+ * About JOINs and dropped columns: although the parser never includes an
+ * already-dropped column in a JOIN RTE's alias var list, it is possible for
+ * such a list in a stored rule to include references to dropped columns.
+ * (If the column is not explicitly referenced anywhere else in the query,
+ * the dependency mechanism won't consider it used by the rule and so won't
+ * prevent the column drop.) To support get_rte_attribute_is_dropped(),
+ * we replace join alias vars that reference dropped columns with NULL Const
+ * nodes.
+ *
+ * (In PostgreSQL 8.0, we did not do this processing but instead had
+ * get_rte_attribute_is_dropped() recurse to detect dropped columns in joins.
+ * That approach had horrible performance unfortunately; in particular
+ * construction of a nested join was O(N^2) in the nesting depth.)
+ */
+void
+AcquireRewriteLocks(Query *parsetree)
+{
+ ListCell *l;
+ int rt_index;
+
+ /*
+ * First, process RTEs of the current query level.
+ */
+ rt_index = 0;
+ foreach(l, parsetree->rtable)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+ Relation rel;
+ LOCKMODE lockmode;
+ List *newaliasvars;
+ ListCell *ll;
+
+ ++rt_index;
+ switch (rte->rtekind)
+ {
+ case RTE_RELATION:
+ /*
+ * 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 we
+ * need RowExclusiveLock. Otherwise, check to see if the
+ * relation is accessed FOR UPDATE/SHARE or not. We can't
+ * just grab AccessShareLock because then the executor
+ * would be trying to upgrade the lock, leading to possible
+ * deadlocks.
+ */
+ if (rt_index == parsetree->resultRelation)
+ lockmode = RowExclusiveLock;
+ else if (list_member_int(parsetree->rowMarks, rt_index))
+ lockmode = RowShareLock;
+ else
+ lockmode = AccessShareLock;
+
+ rel = heap_open(rte->relid, lockmode);
+ heap_close(rel, NoLock);
+ break;
+
+ case RTE_JOIN:
+ /*
+ * Scan the join's alias var list to see if any columns
+ * have been dropped, and if so replace those Vars with
+ * NULL Consts.
+ */
+ newaliasvars = NIL;
+ foreach(ll, rte->joinaliasvars)
+ {
+ Var *aliasvar = (Var *) lfirst(ll);
+
+ /*
+ * If the list item isn't a simple Var, then it must
+ * represent a merged column, ie a USING column, and so it
+ * couldn't possibly be dropped, since it's referenced in
+ * the join clause. (Conceivably it could also be a
+ * NULL constant already? But that's OK too.)
+ */
+ if (IsA(aliasvar, Var))
+ {
+ /*
+ * The elements of an alias list have to refer to
+ * earlier RTEs of the same rtable, because that's
+ * the order the planner builds things in. So we
+ * already processed the referenced RTE, and so it's
+ * safe to use get_rte_attribute_is_dropped on it.
+ * (This might not hold after rewriting or planning,
+ * but it's OK to assume here.)
+ */
+ Assert(aliasvar->varlevelsup == 0);
+ if (aliasvar->varno >= rt_index)
+ elog(ERROR, "unexpected varno %d in JOIN RTE %d",
+ aliasvar->varno, rt_index);
+ if (get_rte_attribute_is_dropped(
+ rt_fetch(aliasvar->varno, parsetree->rtable),
+ aliasvar->varattno))
+ {
+ /*
+ * can't use vartype here, since that might be a
+ * now-dropped type OID, but it doesn't really
+ * matter what type the Const claims to be.
+ */
+ aliasvar = (Var *) makeNullConst(INT4OID);
+ }
+ }
+ newaliasvars = lappend(newaliasvars, aliasvar);
+ }
+ rte->joinaliasvars = newaliasvars;
+ break;
+
+ case RTE_SUBQUERY:
+ /*
+ * The subquery RTE itself is all right, but we have to
+ * recurse to process the represented subquery.
+ */
+ AcquireRewriteLocks(rte->subquery);
+ break;
+
+ default:
+ /* ignore other types of RTEs */
+ break;
+ }
+ }
+
+ /*
+ * Recurse into sublink subqueries, too. But we already did the ones
+ * in the rtable.
+ */
+ if (parsetree->hasSubLinks)
+ query_tree_walker(parsetree, acquireLocksOnSubLinks, NULL,
+ QTW_IGNORE_RT_SUBQUERIES);
+}
+
+/*
+ * Walker to find sublink subqueries for AcquireRewriteLocks
+ */
+static bool
+acquireLocksOnSubLinks(Node *node, void *context)
+{
+ if (node == NULL)
+ return false;
+ if (IsA(node, SubLink))
+ {
+ SubLink *sub = (SubLink *) node;
+
+ /* Do what we came for */
+ AcquireRewriteLocks((Query *) sub->subselect);
+ /* Fall through to process lefthand args of SubLink */
+ }
+
+ /*
+ * Do NOT recurse into Query nodes, because AcquireRewriteLocks already
+ * processed subselects of subselects for us.
+ */
+ return expression_tree_walker(node, acquireLocksOnSubLinks, context);
+}
+
+
+/*
* rewriteRuleAction -
* Rewrite the rule action with appropriate qualifiers (taken from
* the triggering query).
@@ -82,6 +258,12 @@ rewriteRuleAction(Query *parsetree,
rule_action = (Query *) copyObject(rule_action);
rule_qual = (Node *) copyObject(rule_qual);
+ /*
+ * Acquire necessary locks and fix any deleted JOIN RTE entries.
+ */
+ AcquireRewriteLocks(rule_action);
+ (void) acquireLocksOnSubLinks(rule_qual, NULL);
+
current_varno = rt_index;
rt_length = list_length(parsetree->rtable);
new_varno = PRS2_NEW_VARNO + rt_length;
@@ -693,6 +875,9 @@ matchLocks(CmdType event,
}
+/*
+ * ApplyRetrieveRule - expand an ON SELECT rule
+ */
static Query *
ApplyRetrieveRule(Query *parsetree,
RewriteRule *rule,
@@ -713,11 +898,16 @@ ApplyRetrieveRule(Query *parsetree,
elog(ERROR, "cannot handle per-attribute ON SELECT rule");
/*
- * Make a modifiable copy of the view query, and recursively expand
- * any view references inside it.
+ * Make a modifiable copy of the view query, and acquire needed locks
+ * on the relations it mentions.
*/
rule_action = copyObject(linitial(rule->actions));
+ AcquireRewriteLocks(rule_action);
+
+ /*
+ * Recursively expand any view references inside the view.
+ */
rule_action = fireRIRrules(rule_action, activeRIRs);
/*
@@ -868,7 +1058,6 @@ fireRIRrules(Query *parsetree, List *activeRIRs)
List *locks;
RuleLock *rules;
RewriteRule *rule;
- LOCKMODE lockmode;
int i;
++rt_index;
@@ -904,26 +1093,10 @@ fireRIRrules(Query *parsetree, List *activeRIRs)
continue;
/*
- * 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/SHARE or not.
+ * We can use NoLock here since either the parser or
+ * AcquireRewriteLocks should have locked the rel already.
*/
- if (rt_index == parsetree->resultRelation)
- lockmode = NoLock;
- else if (list_member_int(parsetree->rowMarks, rt_index))
- lockmode = RowShareLock;
- else
- lockmode = AccessShareLock;
-
- rel = heap_open(rte->relid, lockmode);
+ rel = heap_open(rte->relid, NoLock);
/*
* Collect the RIR rules that we must apply
@@ -1015,9 +1188,17 @@ CopyAndAddInvertedQual(Query *parsetree,
int rt_index,
CmdType event)
{
- Query *new_tree = (Query *) copyObject(parsetree);
+ /* Don't scribble on the passed qual (it's in the relcache!) */
Node *new_qual = (Node *) copyObject(rule_qual);
+ /*
+ * In case there are subqueries in the qual, acquire necessary locks and
+ * fix any deleted JOIN RTE entries. (This is somewhat redundant with
+ * rewriteRuleAction, but not entirely ... consider restructuring so
+ * that we only need to process the qual this way once.)
+ */
+ (void) acquireLocksOnSubLinks(new_qual, NULL);
+
/* Fix references to OLD */
ChangeVarNodes(new_qual, PRS2_OLD_VARNO, rt_index, 0);
/* Fix references to NEW */
@@ -1030,9 +1211,9 @@ CopyAndAddInvertedQual(Query *parsetree,
event,
rt_index);
/* And attach the fixed qual */
- AddInvertedQual(new_tree, new_qual);
+ AddInvertedQual(parsetree, new_qual);
- return new_tree;
+ return parsetree;
}
@@ -1112,7 +1293,7 @@ fireRules(Query *parsetree,
if (!*instead_flag)
{
if (*qual_product == NULL)
- *qual_product = parsetree;
+ *qual_product = copyObject(parsetree);
*qual_product = CopyAndAddInvertedQual(*qual_product,
event_qual,
rt_index,
@@ -1177,15 +1358,10 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
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.
+ * We can use NoLock here since either the parser or
+ * AcquireRewriteLocks should have locked the rel already.
*/
- rt_entry_relation = heap_open(rt_entry->relid, RowExclusiveLock);
+ rt_entry_relation = heap_open(rt_entry->relid, NoLock);
/*
* If it's an INSERT or UPDATE, rewrite the targetlist into
@@ -1251,7 +1427,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
}
}
- heap_close(rt_entry_relation, NoLock); /* keep lock! */
+ heap_close(rt_entry_relation, NoLock);
}
/*
@@ -1295,8 +1471,8 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
* 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.
+ * NOTE: the parsetree must either have come straight from the parser,
+ * or have been scanned by AcquireRewriteLocks to acquire suitable locks.
*/
List *
QueryRewrite(Query *parsetree)