Make FOR UPDATE/SHARE in the primary query not propagate into WITH queries;
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 27 Oct 2009 17:11:18 +0000 (17:11 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 27 Oct 2009 17:11:18 +0000 (17:11 +0000)
for example in
  WITH w AS (SELECT * FROM foo) SELECT * FROM w, bar ... FOR UPDATE
the FOR UPDATE will now affect bar but not foo.  This is more useful and
consistent than the original 8.4 behavior, which tried to propagate FOR UPDATE
into the WITH query but always failed due to assorted implementation
restrictions.  Even though we are in process of removing those restrictions,
it seems correct on philosophical grounds to not let the outer query's
FOR UPDATE affect the WITH query.

In passing, fix isLockedRel which frequently got things wrong in
nested-subquery cases: "FOR UPDATE OF foo" applies to an alias foo in the
current query level, not subqueries.  This has been broken for a long time,
but it doesn't seem worth back-patching further than 8.4 because the actual
consequences are minimal.  At worst the parser would sometimes get
RowShareLock on a relation when it should be AccessShareLock or vice versa.
That would only make a difference if someone were using ExclusiveLock
concurrently, which no standard operation does, and anyway FOR UPDATE
doesn't result in visible changes so it's not clear that the someone would
notice any problem.  Between that and the fact that FOR UPDATE barely works
with subqueries at all in existing releases, I'm not excited about worrying
about it.

doc/src/sgml/ref/select.sgml
src/backend/parser/analyze.c
src/backend/parser/parse_clause.c
src/backend/parser/parse_cte.c
src/backend/parser/parse_expr.c
src/backend/parser/parse_relation.c
src/backend/rewrite/rewriteHandler.c
src/include/parser/analyze.h
src/include/parser/parse_node.h
src/include/parser/parse_relation.h

index 0c3e397805359628b18b3d0baabb7e76e6aeb6c8..c51bd767eba295ef62959613b787772f06eaedaf 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$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
 -->
 
@@ -1127,6 +1127,11 @@ FOR SHARE [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ]
     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>
index 5fb1b31688dd8f5377b02b2ea0ed74a4c87238be..84cbd2142e338991b3e2823c4da7c42e6c66fef4 100644 (file)
@@ -17,7 +17,7 @@
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -138,12 +138,14 @@ parse_analyze_varparams(Node *parseTree, const char *sourceText,
  */
 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);
 
@@ -1424,7 +1426,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
         * 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
@@ -2051,7 +2053,7 @@ CheckSelectLocking(Query *qry)
  * 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)
@@ -2093,32 +2095,8 @@ 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;
            }
        }
@@ -2185,30 +2163,10 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc)
                             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",
index c3bdf33a998d65c66cc906cd8fd180e0ba89a709..84443db8932dd6f18eef1e18e35ed0cf06b7762d 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -479,7 +479,8 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
    /*
     * 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
index d4a190b505ef5c8f24cb9d6308e6f898e6418b5e..4904d4b39f410c9a9f4b6e4c7c3f4d0ab40c58c2 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -229,7 +229,7 @@ analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
    /* 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;
 
    /*
index c6f3abe7caaaac9a1969a7ba0ce0e83b002472c5..2146329ad89693999ba2624e5cafdf6ad00e1be2 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -1259,7 +1259,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
        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
index 1a5f77d272d7c288a11e37f47ac4bdc1af333164..9b35577ec3c8e2606b398495880b566ec7ecce83 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -38,7 +38,6 @@ static RangeTblEntry *scanNameSpaceForRelid(ParseState *pstate, Oid relid,
                      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,
@@ -909,7 +908,7 @@ addRangeTableEntry(ParseState *pstate,
     * 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);
 
@@ -1454,40 +1453,46 @@ addRangeTableEntryForCTE(ParseState *pstate,
 /*
  * 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;
 }
index a4d9ae556047caecc6deaba57b49a1aaad886694..0ea80e5e8bc80ce94a1fc045ed86b8fd23a65358 100644 (file)
@@ -7,7 +7,7 @@
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -1242,35 +1242,7 @@ markQueryForLocking(Query *qry, Node *jtnode, bool forUpdate, bool noWait)
            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))
    {
index 41282675d4c49a3672d1b35d37029e6a98ed7716..59e64c91455f75485c55716e242d59b30a09246c 100644 (file)
@@ -7,7 +7,7 @@
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -23,7 +23,8 @@ extern Query *parse_analyze_varparams(Node *parseTree, const char *sourceText,
                        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);
index 110ca3fc4a31dd07868432e9df5295312a491cb6..5eb446f12042b695b16f1dd8afec56126b0215b0 100644 (file)
@@ -7,7 +7,7 @@
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -103,6 +103,7 @@ typedef struct ParseState
    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;
index 69a914376c7f21d5f2b9653554b73667d82369f4..96b60de480e6ecea2b4bc3c436f02e9dcc547ed3 100644 (file)
@@ -7,7 +7,7 @@
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -81,6 +81,7 @@ extern RangeTblEntry *addRangeTableEntryForCTE(ParseState *pstate,
                         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);