aboutsummaryrefslogtreecommitdiff
path: root/src/backend/parser
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/parser')
-rw-r--r--src/backend/parser/analyze.c30
-rw-r--r--src/backend/parser/gram.y12
-rw-r--r--src/backend/parser/parse_clause.c37
-rw-r--r--src/backend/parser/parse_relation.c61
4 files changed, 114 insertions, 26 deletions
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 93edc9f8781..63f39f2e4cb 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: analyze.c,v 1.164 2000/11/05 01:42:07 tgl Exp $
+ * $Id: analyze.c,v 1.165 2000/11/08 22:09:58 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -115,7 +115,7 @@ static void
release_pstate_resources(ParseState *pstate)
{
if (pstate->p_target_relation != NULL)
- heap_close(pstate->p_target_relation, AccessShareLock);
+ heap_close(pstate->p_target_relation, NoLock);
pstate->p_target_relation = NULL;
pstate->p_target_rangetblentry = NULL;
}
@@ -292,6 +292,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
qry->commandType = CMD_DELETE;
/* set up a range table */
+ lockTargetTable(pstate, stmt->relname);
makeRangeTable(pstate, NIL);
setTargetTable(pstate, stmt->relname, stmt->inh, true);
@@ -332,6 +333,13 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
pstate->p_is_insert = true;
/*
+ * Must get write lock on target table before scanning SELECT,
+ * else we will grab the wrong kind of initial lock if the target
+ * table is also mentioned in the SELECT part.
+ */
+ lockTargetTable(pstate, stmt->relname);
+
+ /*
* Is it INSERT ... SELECT or INSERT ... VALUES?
*/
if (stmt->selectStmt)
@@ -1522,6 +1530,16 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt)
qry->utilityStmt = (Node *) stmt;
/*
+ * To avoid deadlock, make sure the first thing we do is grab
+ * AccessExclusiveLock on the target relation. This will be
+ * needed by DefineQueryRewrite(), and we don't want to grab a lesser
+ * lock beforehand. We don't need to hold a refcount on the relcache
+ * entry, however.
+ */
+ heap_close(heap_openr(stmt->object->relname, AccessExclusiveLock),
+ NoLock);
+
+ /*
* NOTE: 'OLD' must always have a varno equal to 1 and 'NEW'
* equal to 2. Set up their RTEs in the main pstate for use
* in parsing the rule qualification.
@@ -1727,6 +1745,9 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
qry->isBinary = FALSE;
}
+ /* make FOR UPDATE clause available to addRangeTableEntry */
+ pstate->p_forUpdate = stmt->forUpdate;
+
/* set up a range table */
makeRangeTable(pstate, stmt->fromClause);
@@ -1765,7 +1786,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
- if (stmt->forUpdate != NULL)
+ if (stmt->forUpdate != NIL)
transformForUpdate(qry, stmt->forUpdate);
return qry;
@@ -1951,7 +1972,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
- if (forUpdate != NULL)
+ if (forUpdate != NIL)
transformForUpdate(qry, forUpdate);
return qry;
@@ -2159,6 +2180,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
* used in FROM, we'd fail to notice that it should be marked
* checkForRead as well as checkForWrite. See setTargetTable().
*/
+ lockTargetTable(pstate, stmt->relname);
makeRangeTable(pstate, stmt->fromClause);
setTargetTable(pstate, stmt->relname, stmt->inh, true);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 4fbc628c582..5572828d259 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.207 2000/11/08 21:28:06 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.208 2000/11/08 22:09:58 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@@ -334,7 +334,7 @@ static void doNegateFloat(Value *v);
* when some sort of pg_privileges relation is introduced.
* - Todd A. Brandys 1998-01-01?
*/
-%token ABORT_TRANS, ACCESS, AFTER, AGGREGATE, ANALYZE, ANALYSE /* British */
+%token ABORT_TRANS, ACCESS, AFTER, AGGREGATE, ANALYZE, ANALYSE,
BACKWARD, BEFORE, BINARY, BIT,
CACHE, CHECKPOINT, CLUSTER, COMMENT, COPY, CREATEDB, CREATEUSER, CYCLE,
DATABASE, DELIMITERS, DO,
@@ -2466,11 +2466,7 @@ ExtendStmt: EXTEND INDEX index_name where_clause
/* NOT USED
RecipeStmt: EXECUTE RECIPE recipe_name
{
- RecipeStmt *n;
- if (!IsTransactionBlock())
- elog(ERROR,"EXECUTE RECIPE may only be used in begin/end transaction blocks");
-
- n = makeNode(RecipeStmt);
+ RecipeStmt *n = makeNode(RecipeStmt);
n->recipeName = $3;
$$ = (Node *)n;
}
@@ -2633,8 +2629,6 @@ oper_argtypes: Typename
ReindexStmt: REINDEX reindex_type name opt_force
{
ReindexStmt *n = makeNode(ReindexStmt);
- if (IsTransactionBlock())
- elog(ERROR,"REINDEX command could only be used outside begin/end transaction blocks");
n->reindexType = $2;
n->name = $3;
n->force = $4;
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 38dc3ea0976..60521d13475 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.70 2000/10/07 00:58:17 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.71 2000/11/08 22:09:58 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -88,6 +88,34 @@ makeRangeTable(ParseState *pstate, List *frmList)
}
/*
+ * lockTargetTable
+ * Find the target relation of INSERT/UPDATE/DELETE and acquire write
+ * lock on it. This must be done before building the range table,
+ * in case the target is also mentioned as a source relation --- we
+ * want to be sure to grab the write lock before any read lock.
+ *
+ * The ParseState's link to the target relcache entry is also set here.
+ */
+void
+lockTargetTable(ParseState *pstate, char *relname)
+{
+ /* Close old target; this could only happen for multi-action rules */
+ if (pstate->p_target_relation != NULL)
+ heap_close(pstate->p_target_relation, NoLock);
+ pstate->p_target_relation = NULL;
+ pstate->p_target_rangetblentry = NULL; /* setTargetTable will set this */
+
+ /*
+ * Open target rel and grab suitable lock (which we will hold till
+ * end of transaction).
+ *
+ * analyze.c will eventually do the corresponding heap_close(),
+ * but *not* release the lock.
+ */
+ pstate->p_target_relation = heap_openr(relname, RowExclusiveLock);
+}
+
+/*
* setTargetTable
* Add the target relation of INSERT/UPDATE/DELETE to the range table,
* and make the special links to it in the ParseState.
@@ -133,13 +161,10 @@ setTargetTable(ParseState *pstate, char *relname, bool inh, bool inJoinSet)
if (inJoinSet)
addRTEtoJoinList(pstate, rte);
- /* This could only happen for multi-action rules */
- if (pstate->p_target_relation != NULL)
- heap_close(pstate->p_target_relation, AccessShareLock);
+ /* lockTargetTable should have been called earlier */
+ Assert(pstate->p_target_relation != NULL);
pstate->p_target_rangetblentry = rte;
- pstate->p_target_relation = heap_open(rte->relid, AccessShareLock);
- /* will close relation later, see analyze.c */
}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 3fccd95cb18..984485f9b45 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.49 2000/09/29 18:21:36 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.50 2000/11/08 22:09:58 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -34,6 +34,7 @@ static Node *scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte,
char *colname);
static Node *scanJoinForColumn(JoinExpr *join, char *colname,
int sublevels_up);
+static bool isForUpdate(ParseState *pstate, char *relname);
static List *expandNamesVars(ParseState *pstate, List *names, List *vars);
static void warnAutoRange(ParseState *pstate, char *refname);
@@ -477,6 +478,7 @@ addRangeTableEntry(ParseState *pstate,
bool inFromCl)
{
char *refname = alias ? alias->relname : relname;
+ LOCKMODE lockmode;
Relation rel;
RangeTblEntry *rte;
Attr *eref;
@@ -502,17 +504,22 @@ addRangeTableEntry(ParseState *pstate,
/*
* Get the rel's OID. This access also ensures that we have an
- * up-to-date relcache entry for the rel. We don't need to keep it
- * open, however. Since this is open anyway, let's check that the
- * number of column aliases is reasonable. - Thomas 2000-02-04
+ * up-to-date relcache entry for the rel. Since this is typically
+ * the first access to a rel in a statement, be careful to get the
+ * right access level depending on whether we're doing SELECT FOR UPDATE.
*/
- rel = heap_openr(relname, AccessShareLock);
+ lockmode = isForUpdate(pstate, relname) ? RowShareLock : AccessShareLock;
+ rel = heap_openr(relname, lockmode);
rte->relid = RelationGetRelid(rel);
- maxattrs = RelationGetNumberOfAttributes(rel);
eref = alias ? (Attr *) copyObject(alias) : makeAttr(refname, NULL);
numaliases = length(eref->attrs);
+ /*
+ * Since the rel is open anyway, let's check that the
+ * number of column aliases is reasonable. - Thomas 2000-02-04
+ */
+ maxattrs = RelationGetNumberOfAttributes(rel);
if (maxattrs < numaliases)
elog(ERROR, "Table \"%s\" has %d columns available but %d columns specified",
refname, maxattrs, numaliases);
@@ -527,7 +534,12 @@ addRangeTableEntry(ParseState *pstate,
}
rte->eref = eref;
- heap_close(rel, AccessShareLock);
+ /*
+ * Drop the rel refcount, but keep the access lock till end of transaction
+ * so that the table can't be deleted or have its schema modified
+ * underneath us.
+ */
+ heap_close(rel, NoLock);
/*----------
* Flags:
@@ -644,6 +656,41 @@ addRangeTableEntryForSubquery(ParseState *pstate,
}
/*
+ * Has the specified relname been selected FOR UPDATE?
+ */
+static bool
+isForUpdate(ParseState *pstate, char *relname)
+{
+ /* Outer loop to check parent query levels as well as this one */
+ while (pstate != NULL)
+ {
+ if (pstate->p_forUpdate != NIL)
+ {
+ if (lfirst(pstate->p_forUpdate) == NULL)
+ {
+ /* all tables used in query */
+ return true;
+ }
+ else
+ {
+ /* just the named tables */
+ List *l;
+
+ foreach(l, pstate->p_forUpdate)
+ {
+ char *rname = lfirst(l);
+
+ if (strcmp(relname, rname) == 0)
+ return true;
+ }
+ }
+ }
+ pstate = pstate->parentParseState;
+ }
+ return false;
+}
+
+/*
* Add the given RTE as a top-level entry in the pstate's join list,
* unless there already is an entry for it.
*/