<!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/select.sgml,v 1.126 2009/10/21 20:22:38 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/select.sgml,v 1.127 2009/10/27 17:11:18 tgl Exp $
PostgreSQL documentation
-->
If <literal>FOR UPDATE</literal> or <literal>FOR SHARE</literal> is
applied to a view or sub-query, it affects all tables used in
the view or sub-query.
+ However, <literal>FOR UPDATE</literal>/<literal>FOR SHARE</literal>
+ do not apply to <literal>WITH</> queries referenced by the primary query.
+ If you want row locking to occur within a <literal>WITH</> query, specify
+ <literal>FOR UPDATE</literal> or <literal>FOR SHARE</literal> within the
+ <literal>WITH</> query.
</para>
<para>
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.393 2009/10/26 02:26:35 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.394 2009/10/27 17:11:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
*/
Query *
parse_sub_analyze(Node *parseTree, ParseState *parentParseState,
- CommonTableExpr *parentCTE)
+ CommonTableExpr *parentCTE,
+ bool locked_from_parent)
{
ParseState *pstate = make_parsestate(parentParseState);
Query *query;
pstate->p_parent_cte = parentCTE;
+ pstate->p_locked_from_parent = locked_from_parent;
query = transformStmt(pstate, parseTree);
* of this sub-query, because they are not in the toplevel pstate's
* namespace list.
*/
- selectQuery = parse_sub_analyze((Node *) stmt, pstate, NULL);
+ selectQuery = parse_sub_analyze((Node *) stmt, pstate, NULL, false);
/*
* Check for bogus references to Vars on the current query level (but
* This basically involves replacing names by integer relids.
*
* NB: if you need to change this, see also markQueryForLocking()
- * in rewriteHandler.c, and isLockedRel() in parse_relation.c.
+ * in rewriteHandler.c, and isLockedRefname() in parse_relation.c.
*/
static void
transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc)
*/
transformLockingClause(pstate, rte->subquery, allrels);
break;
- case RTE_CTE:
- {
- /*
- * We allow FOR UPDATE/SHARE of a WITH query to be
- * propagated into the WITH, but it doesn't seem very
- * sane to allow this for a reference to an
- * outer-level WITH. And it definitely wouldn't work
- * for a self-reference, since we're not done
- * analyzing the CTE anyway.
- */
- CommonTableExpr *cte;
-
- if (rte->ctelevelsup > 0 || rte->self_reference)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SELECT FOR UPDATE/SHARE cannot be applied to an outer-level WITH query")));
- cte = GetCTEForRTE(pstate, rte, -1);
- /* should be analyzed by now */
- Assert(IsA(cte->ctequery, Query));
- transformLockingClause(pstate,
- (Query *) cte->ctequery,
- allrels);
- }
- break;
default:
- /* ignore JOIN, SPECIAL, FUNCTION RTEs */
+ /* ignore JOIN, SPECIAL, FUNCTION, VALUES, CTE RTEs */
break;
}
}
parser_errposition(pstate, thisrel->location)));
break;
case RTE_CTE:
- {
- /*
- * We allow FOR UPDATE/SHARE of a WITH query
- * to be propagated into the WITH, but it
- * doesn't seem very sane to allow this for a
- * reference to an outer-level WITH. And it
- * definitely wouldn't work for a
- * self-reference, since we're not done
- * analyzing the CTE anyway.
- */
- CommonTableExpr *cte;
-
- if (rte->ctelevelsup > 0 || rte->self_reference)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SELECT FOR UPDATE/SHARE cannot be applied to an outer-level WITH query"),
- parser_errposition(pstate, thisrel->location)));
- cte = GetCTEForRTE(pstate, rte, -1);
- /* should be analyzed by now */
- Assert(IsA(cte->ctequery, Query));
- transformLockingClause(pstate,
- (Query *) cte->ctequery,
- allrels);
- }
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SELECT FOR UPDATE/SHARE cannot be applied to a WITH query"),
+ parser_errposition(pstate, thisrel->location)));
break;
default:
elog(ERROR, "unrecognized RTE type: %d",
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.192 2009/09/09 03:32:52 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.193 2009/10/27 17:11:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
/*
* Analyze and transform the subquery.
*/
- query = parse_sub_analyze(r->subquery, pstate, NULL);
+ query = parse_sub_analyze(r->subquery, pstate, NULL,
+ isLockedRefname(pstate, r->alias->aliasname));
/*
* Check that we got something reasonable. Many of these conditions are
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/parse_cte.c,v 2.7 2009/09/09 03:32:52 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/parse_cte.c,v 2.8 2009/10/27 17:11:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
/* Analysis not done already */
Assert(IsA(cte->ctequery, SelectStmt));
- query = parse_sub_analyze(cte->ctequery, pstate, cte);
+ query = parse_sub_analyze(cte->ctequery, pstate, cte, false);
cte->ctequery = (Node *) query;
/*
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.245 2009/10/21 20:22:38 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.246 2009/10/27 17:11:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
return result;
pstate->p_hasSubLinks = true;
- qtree = parse_sub_analyze(sublink->subselect, pstate, NULL);
+ qtree = parse_sub_analyze(sublink->subselect, pstate, NULL, false);
/*
* Check that we got something reasonable. Many of these conditions are
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.145 2009/10/26 02:26:35 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.146 2009/10/27 17:11:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
int location);
static void markRTEForSelectPriv(ParseState *pstate, RangeTblEntry *rte,
int rtindex, AttrNumber col);
-static bool isLockedRel(ParseState *pstate, char *refname);
static void expandRelation(Oid relid, Alias *eref,
int rtindex, int sublevels_up,
int location, bool include_dropped,
* to a rel in a statement, be careful to get the right access level
* depending on whether we're doing SELECT FOR UPDATE/SHARE.
*/
- lockmode = isLockedRel(pstate, refname) ? RowShareLock : AccessShareLock;
+ lockmode = isLockedRefname(pstate, refname) ? RowShareLock : AccessShareLock;
rel = parserOpenTable(pstate, relation, lockmode);
rte->relid = RelationGetRelid(rel);
/*
* Has the specified refname been selected FOR UPDATE/FOR SHARE?
*
- * Note: we pay no attention to whether it's FOR UPDATE vs FOR SHARE.
+ * This is used when we have not yet done transformLockingClause, but need
+ * to know the correct lock to take during initial opening of relations.
+ *
+ * Note: we pay no attention to whether it's FOR UPDATE vs FOR SHARE,
+ * since the table-level lock is the same either way.
*/
-static bool
-isLockedRel(ParseState *pstate, char *refname)
+bool
+isLockedRefname(ParseState *pstate, const char *refname)
{
- /* Outer loop to check parent query levels as well as this one */
- while (pstate != NULL)
+ ListCell *l;
+
+ /*
+ * If we are in a subquery specified as locked FOR UPDATE/SHARE from
+ * parent level, then act as though there's a generic FOR UPDATE here.
+ */
+ if (pstate->p_locked_from_parent)
+ return true;
+
+ foreach(l, pstate->p_locking_clause)
{
- ListCell *l;
+ LockingClause *lc = (LockingClause *) lfirst(l);
- foreach(l, pstate->p_locking_clause)
+ if (lc->lockedRels == NIL)
{
- LockingClause *lc = (LockingClause *) lfirst(l);
+ /* all tables used in query */
+ return true;
+ }
+ else
+ {
+ /* just the named tables */
+ ListCell *l2;
- if (lc->lockedRels == NIL)
- {
- /* all tables used in query */
- return true;
- }
- else
+ foreach(l2, lc->lockedRels)
{
- /* just the named tables */
- ListCell *l2;
-
- foreach(l2, lc->lockedRels)
- {
- RangeVar *thisrel = (RangeVar *) lfirst(l2);
+ RangeVar *thisrel = (RangeVar *) lfirst(l2);
- if (strcmp(refname, thisrel->relname) == 0)
- return true;
- }
+ if (strcmp(refname, thisrel->relname) == 0)
+ return true;
}
}
- pstate = pstate->parentParseState;
}
return false;
}
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.188 2009/10/26 02:26:36 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.189 2009/10/27 17:11:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
markQueryForLocking(rte->subquery, (Node *) rte->subquery->jointree,
forUpdate, noWait);
}
- else if (rte->rtekind == RTE_CTE)
- {
- /*
- * We allow FOR UPDATE/SHARE of a WITH query to be propagated into
- * the WITH, but it doesn't seem very sane to allow this for a
- * reference to an outer-level WITH (compare
- * transformLockingClause). Which simplifies life here.
- */
- CommonTableExpr *cte = NULL;
- ListCell *lc;
-
- if (rte->ctelevelsup > 0 || rte->self_reference)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SELECT FOR UPDATE/SHARE cannot be applied to an outer-level WITH query")));
- foreach(lc, qry->cteList)
- {
- cte = (CommonTableExpr *) lfirst(lc);
- if (strcmp(cte->ctename, rte->ctename) == 0)
- break;
- }
- if (lc == NULL) /* shouldn't happen */
- elog(ERROR, "could not find CTE \"%s\"", rte->ctename);
- /* should be analyzed by now */
- Assert(IsA(cte->ctequery, Query));
- markQueryForLocking((Query *) cte->ctequery,
- (Node *) ((Query *) cte->ctequery)->jointree,
- forUpdate, noWait);
- }
+ /* other RTE types are unaffected by FOR UPDATE */
}
else if (IsA(jtnode, FromExpr))
{
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/parser/analyze.h,v 1.41 2009/09/09 03:32:52 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/parser/analyze.h,v 1.42 2009/10/27 17:11:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
Oid **paramTypes, int *numParams);
extern Query *parse_sub_analyze(Node *parseTree, ParseState *parentParseState,
- CommonTableExpr *parentCTE);
+ CommonTableExpr *parentCTE,
+ bool locked_from_parent);
extern Query *transformStmt(ParseState *pstate, Node *parseTree);
extern bool analyze_requires_snapshot(Node *parseTree);
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/parser/parse_node.h,v 1.64 2009/10/21 20:22:38 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/parser/parse_node.h,v 1.65 2009/10/27 17:11:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
bool p_hasSubLinks;
bool p_is_insert;
bool p_is_update;
+ bool p_locked_from_parent;
Relation p_target_relation;
RangeTblEntry *p_target_rangetblentry;
} ParseState;
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/parser/parse_relation.h,v 1.65 2009/10/21 20:22:38 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/parser/parse_relation.h,v 1.66 2009/10/27 17:11:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
Index levelsup,
Alias *alias,
bool inFromCl);
+extern bool isLockedRefname(ParseState *pstate, const char *refname);
extern void addRTEtoQuery(ParseState *pstate, RangeTblEntry *rte,
bool addToJoinList,
bool addToRelNameSpace, bool addToVarNameSpace);