aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2014-03-06 19:31:22 -0500
committerTom Lane <tgl@sss.pgh.pa.us>2014-03-06 19:31:22 -0500
commitf043bddfe0c1a3f31187849c2727d2eecdf06bcc (patch)
tree28099051dbd2fe9459608e7d0c5b01b55a63a065
parentb6e143458505708ac139520f067060f66b222e5b (diff)
downloadpostgresql-f043bddfe0c1a3f31187849c2727d2eecdf06bcc.tar.gz
postgresql-f043bddfe0c1a3f31187849c2727d2eecdf06bcc.zip
Avoid getting more than AccessShareLock when deparsing a query.
In make_ruledef and get_query_def, we have long used AcquireRewriteLocks to ensure that the querytree we are about to deparse is up-to-date and the schemas of the underlying relations aren't changing. Howwever, that function thinks the query is about to be executed, so it acquires locks that are stronger than necessary for the purpose of deparsing. Thus for example, if pg_dump asks to deparse a rule that includes "INSERT INTO t", we'd acquire RowExclusiveLock on t. That results in interference with concurrent transactions that might for example ask for ShareLock on t. Since pg_dump is documented as being purely read-only, this is unexpected. (Worse, it used to actually be read-only; this behavior dates back only to 8.1, cf commit ba4200246.) Fix this by adding a parameter to AcquireRewriteLocks to tell it whether we want the "real" execution locks or only AccessShareLock. Report, diagnosis, and patch by Dean Rasheed. Back-patch to all supported branches.
-rw-r--r--src/backend/rewrite/rewriteHandler.c58
-rw-r--r--src/backend/utils/adt/ruleutils.c7
-rw-r--r--src/include/rewrite/rewriteHandler.h2
3 files changed, 46 insertions, 21 deletions
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 8d9805013bb..4e7b307ef2e 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -36,7 +36,13 @@ typedef struct rewrite_event
CmdType event; /* type of rule being fired */
} rewrite_event;
-static bool acquireLocksOnSubLinks(Node *node, void *context);
+typedef struct acquireLocksOnSubLinks_context
+{
+ bool for_execute; /* AcquireRewriteLocks' forExecute param */
+} acquireLocksOnSubLinks_context;
+
+static bool acquireLocksOnSubLinks(Node *node,
+ acquireLocksOnSubLinks_context *context);
static Query *rewriteRuleAction(Query *parsetree,
Query *rule_action,
Node *rule_qual,
@@ -65,6 +71,15 @@ static Query *fireRIRrules(Query *parsetree, List *activeRIRs);
* These locks will ensure that the relation schemas don't change under us
* while we are rewriting and planning the query.
*
+ * forExecute indicates that the query is about to be executed.
+ * If so, we'll acquire RowExclusiveLock on the query's resultRelation,
+ * RowShareLock on any relation accessed FOR UPDATE/SHARE, and
+ * AccessShareLock on all other relations mentioned.
+ *
+ * If forExecute is false, AccessShareLock is acquired on all relations.
+ * This case is suitable for ruleutils.c, for example, where we only need
+ * schema stability and we don't intend to actually modify any relations.
+ *
* 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
@@ -91,10 +106,13 @@ static Query *fireRIRrules(Query *parsetree, List *activeRIRs);
* construction of a nested join was O(N^2) in the nesting depth.)
*/
void
-AcquireRewriteLocks(Query *parsetree)
+AcquireRewriteLocks(Query *parsetree, bool forExecute)
{
ListCell *l;
int rt_index;
+ acquireLocksOnSubLinks_context context;
+
+ context.for_execute = forExecute;
/*
* First, process RTEs of the current query level.
@@ -120,14 +138,12 @@ AcquireRewriteLocks(Query *parsetree)
* 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.
+ * Assuming forExecute is true, this logic must match what the
+ * executor will do, else we risk lock-upgrade deadlocks.
*/
- if (rt_index == parsetree->resultRelation)
+ if (!forExecute)
+ lockmode = AccessShareLock;
+ else if (rt_index == parsetree->resultRelation)
lockmode = RowExclusiveLock;
else if (get_rowmark(parsetree, rt_index))
lockmode = RowShareLock;
@@ -206,7 +222,7 @@ AcquireRewriteLocks(Query *parsetree)
* The subquery RTE itself is all right, but we have to
* recurse to process the represented subquery.
*/
- AcquireRewriteLocks(rte->subquery);
+ AcquireRewriteLocks(rte->subquery, forExecute);
break;
default:
@@ -220,7 +236,7 @@ AcquireRewriteLocks(Query *parsetree)
{
CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
- AcquireRewriteLocks((Query *) cte->ctequery);
+ AcquireRewriteLocks((Query *) cte->ctequery, forExecute);
}
/*
@@ -228,7 +244,7 @@ AcquireRewriteLocks(Query *parsetree)
* the rtable and cteList.
*/
if (parsetree->hasSubLinks)
- query_tree_walker(parsetree, acquireLocksOnSubLinks, NULL,
+ query_tree_walker(parsetree, acquireLocksOnSubLinks, &context,
QTW_IGNORE_RC_SUBQUERIES);
}
@@ -236,7 +252,7 @@ AcquireRewriteLocks(Query *parsetree)
* Walker to find sublink subqueries for AcquireRewriteLocks
*/
static bool
-acquireLocksOnSubLinks(Node *node, void *context)
+acquireLocksOnSubLinks(Node *node, acquireLocksOnSubLinks_context *context)
{
if (node == NULL)
return false;
@@ -245,7 +261,7 @@ acquireLocksOnSubLinks(Node *node, void *context)
SubLink *sub = (SubLink *) node;
/* Do what we came for */
- AcquireRewriteLocks((Query *) sub->subselect);
+ AcquireRewriteLocks((Query *) sub->subselect, context->for_execute);
/* Fall through to process lefthand args of SubLink */
}
@@ -287,6 +303,9 @@ rewriteRuleAction(Query *parsetree,
int rt_length;
Query *sub_action;
Query **sub_action_ptr;
+ acquireLocksOnSubLinks_context context;
+
+ context.for_execute = true;
/*
* Make modifiable copies of rule action and qual (what we're passed are
@@ -298,8 +317,8 @@ rewriteRuleAction(Query *parsetree,
/*
* Acquire necessary locks and fix any deleted JOIN RTE entries.
*/
- AcquireRewriteLocks(rule_action);
- (void) acquireLocksOnSubLinks(rule_qual, NULL);
+ AcquireRewriteLocks(rule_action, true);
+ (void) acquireLocksOnSubLinks(rule_qual, &context);
current_varno = rt_index;
rt_length = list_length(parsetree->rtable);
@@ -1154,7 +1173,7 @@ ApplyRetrieveRule(Query *parsetree,
*/
rule_action = copyObject(linitial(rule->actions));
- AcquireRewriteLocks(rule_action);
+ AcquireRewriteLocks(rule_action, true);
/*
* Recursively expand any view references inside the view.
@@ -1465,6 +1484,9 @@ CopyAndAddInvertedQual(Query *parsetree,
{
/* Don't scribble on the passed qual (it's in the relcache!) */
Node *new_qual = (Node *) copyObject(rule_qual);
+ acquireLocksOnSubLinks_context context;
+
+ context.for_execute = true;
/*
* In case there are subqueries in the qual, acquire necessary locks and
@@ -1472,7 +1494,7 @@ CopyAndAddInvertedQual(Query *parsetree,
* rewriteRuleAction, but not entirely ... consider restructuring so that
* we only need to process the qual this way once.)
*/
- (void) acquireLocksOnSubLinks(new_qual, NULL);
+ (void) acquireLocksOnSubLinks(new_qual, &context);
/* Fix references to OLD */
ChangeVarNodes(new_qual, PRS2_OLD_VARNO, rt_index, 0);
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 1969c41db78..117c6259179 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -2126,7 +2126,7 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
query = getInsertSelectQuery(query, NULL);
/* Must acquire locks right away; see notes in get_query_def() */
- AcquireRewriteLocks(query);
+ AcquireRewriteLocks(query, false);
context.buf = buf;
context.namespaces = list_make1(&dpns);
@@ -2270,8 +2270,11 @@ get_query_def(Query *query, StringInfo buf, List *parentnamespace,
* relations, and fix up deleted columns in JOIN RTEs. This ensures
* consistent results. Note we assume it's OK to scribble on the passed
* querytree!
+ *
+ * We are only deparsing the query (we are not about to execute it), so we
+ * only need AccessShareLock on the relations it mentions.
*/
- AcquireRewriteLocks(query);
+ AcquireRewriteLocks(query, false);
context.buf = buf;
context.namespaces = lcons(&dpns, list_copy(parentnamespace));
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 4caef867578..46bc273a49d 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -18,7 +18,7 @@
#include "nodes/parsenodes.h"
extern List *QueryRewrite(Query *parsetree);
-extern void AcquireRewriteLocks(Query *parsetree);
+extern void AcquireRewriteLocks(Query *parsetree, bool forExecute);
extern Node *build_column_default(Relation rel, int attrno);
#endif /* REWRITEHANDLER_H */