]> granicus.if.org Git - postgresql/commitdiff
When FOR UPDATE/SHARE is used with LIMIT, put the LockRows plan node
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 28 Oct 2009 14:55:47 +0000 (14:55 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 28 Oct 2009 14:55:47 +0000 (14:55 +0000)
underneath the Limit node, not atop it.  This fixes the old problem that such
a query might unexpectedly return fewer rows than the LIMIT says, due to
LockRows discarding updated rows.

There is a related problem that LockRows might destroy the sort ordering
produced by earlier steps; but fixing that by pushing LockRows below Sort
would create serious performance problems that are unjustified in many
real-world applications, as well as potential deadlock problems from locking
many more rows than expected.  Instead, keep the present semantics of applying
FOR UPDATE after ORDER BY within a single query level; but allow the user to
specify the other way by writing FOR UPDATE in a sub-select.  To make that
work, track whether FOR UPDATE appeared explicitly in sub-selects or got
pushed down from the parent, and don't flatten a sub-select that contained an
explicit FOR UPDATE.

13 files changed:
doc/src/sgml/ref/select.sgml
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/outfuncs.c
src/backend/nodes/readfuncs.c
src/backend/optimizer/plan/planner.c
src/backend/optimizer/prep/prepjointree.c
src/backend/parser/analyze.c
src/backend/rewrite/rewriteHandler.c
src/backend/utils/adt/ruleutils.c
src/include/catalog/catversion.h
src/include/nodes/parsenodes.h
src/include/parser/analyze.h

index c51bd767eba295ef62959613b787772f06eaedaf..804a697e49683f88121ae4cc44a6b7f863173244 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/select.sgml,v 1.127 2009/10/27 17:11:18 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/select.sgml,v 1.128 2009/10/28 14:55:37 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -1092,22 +1092,12 @@ FOR SHARE [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ]
     has already locked a selected row or rows, <command>SELECT FOR
     UPDATE</command> will wait for the other transaction to complete,
     and will then lock and return the updated row (or no row, if the
-    row was deleted).  For further discussion see <xref
+    row was deleted).  Within a <literal>SERIALIZABLE</> transaction,
+    however, an error will be thrown if a row to be locked has changed
+    since the transaction started.  For further discussion see <xref
     linkend="mvcc">.
    </para>
 
-   <para>
-    To prevent the operation from waiting for other transactions to commit,
-    use the <literal>NOWAIT</> option.  <command>SELECT FOR UPDATE
-    NOWAIT</command> reports an error, rather than waiting, if a selected row
-    cannot be locked immediately.  Note that <literal>NOWAIT</> applies only
-    to the row-level lock(s) &mdash; the required <literal>ROW SHARE</literal>
-    table-level lock is still taken in the ordinary way (see
-    <xref linkend="mvcc">).  You can use the <literal>NOWAIT</> option of
-    <xref linkend="sql-lock" endterm="sql-lock-title">
-    if you need to acquire the table-level lock without waiting.
-   </para>
-
    <para>
     <literal>FOR SHARE</literal> behaves similarly, except that it
     acquires a shared rather than exclusive lock on each retrieved
@@ -1117,13 +1107,26 @@ FOR SHARE [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ]
     from performing <command>SELECT FOR SHARE</command>.
    </para>
 
+   <para>
+    To prevent the operation from waiting for other transactions to commit,
+    use the <literal>NOWAIT</> option.  With <literal>NOWAIT</>, the statement
+    reports an error, rather than waiting, if a selected row
+    cannot be locked immediately.  Note that <literal>NOWAIT</> applies only
+    to the row-level lock(s) &mdash; the required <literal>ROW SHARE</literal>
+    table-level lock is still taken in the ordinary way (see
+    <xref linkend="mvcc">).  You can use
+    <xref linkend="sql-lock" endterm="sql-lock-title">
+    with the <literal>NOWAIT</> option first,
+    if you need to acquire the table-level lock without waiting.
+   </para>
+
    <para>
     If specific tables are named in <literal>FOR UPDATE</literal>
     or <literal>FOR SHARE</literal>,
     then only rows coming from those tables are locked; any other
     tables used in the <command>SELECT</command> are simply read as
     usual.  A <literal>FOR UPDATE</literal> or <literal>FOR SHARE</literal>
-    clause without a table list affects all tables used in the command.
+    clause without a table list affects all tables used in the statement.
     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.
@@ -1151,6 +1154,36 @@ FOR SHARE [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ]
     individual table rows; for example they cannot be used with aggregation.
    </para>
 
+   <para>
+    When <literal>FOR UPDATE</literal> or <literal>FOR SHARE</literal>
+    appears at the top level of a <command>SELECT</> query, the rows that
+    are locked are exactly those that are returned by the query; in the
+    case of a join query, the rows locked are those that contribute to
+    returned join rows.  In addition, rows that satisfied the query
+    conditions as of the query snapshot will be locked, although they
+    will not be returned if they have since been updated to not satisfy
+    the query conditions.  If a <literal>LIMIT</> is used, locking stops
+    once enough rows have been returned to satisfy the limit (but note that
+    rows skipped over by <literal>OFFSET</> will get locked).  Similarly,
+    if <literal>FOR UPDATE</literal> or <literal>FOR SHARE</literal>
+    is used in a cursor's query, only rows actually fetched or stepped past
+    by the cursor will be locked.
+   </para>
+
+   <para>
+    When <literal>FOR UPDATE</literal> or <literal>FOR SHARE</literal>
+    appears in a sub-<command>SELECT</>, the rows locked are those
+    returned to the outer query by the sub-query.  This might involve
+    fewer rows than inspection of the sub-query alone would suggest,
+    since conditions from the outer query might be used to optimize
+    execution of the sub-query.  For example,
+<programlisting>
+SELECT * FROM (SELECT * FROM mytable FOR UPDATE) ss WHERE col1 = 5;
+</programlisting>
+    will lock only rows having <literal>col1 = 5</>, even though that
+    condition is not textually within the sub-query.
+   </para>
+
   <caution>
    <para>
     Avoid locking a row and then modifying it within a later savepoint or
@@ -1177,30 +1210,26 @@ ROLLBACK TO s;
 
   <caution>
    <para>
-    It is possible for a <command>SELECT</> command using both
-    <literal>LIMIT</literal> and <literal>FOR UPDATE/SHARE</literal>
-    clauses to return fewer rows than specified by <literal>LIMIT</literal>.
-    This is because <literal>LIMIT</> is applied first.  The command
-    selects the specified number of rows,
-    but might then block trying to obtain a lock on one or more of them.
-    Once the <literal>SELECT</> unblocks, the row might have been deleted
-    or updated so that it does not meet the query <literal>WHERE</> condition
-    anymore, in which case it will not be returned.
-   </para>
-  </caution>
-
-  <caution>
-   <para>
-    Similarly, it is possible for a <command>SELECT</> command
-    using <literal>ORDER BY</literal> and <literal>FOR
-    UPDATE/SHARE</literal> to return rows out of order.  This is
-    because <literal>ORDER BY</> is applied first.  The command
-    orders the result, but might then block trying to obtain a lock
-    on one or more of the rows.  Once the <literal>SELECT</>
-    unblocks, one of the ordered columns might have been modified
-    and be returned out of order.  A workaround is to perform
-    <command>SELECT ... FOR UPDATE/SHARE</> and then <command>SELECT
-    ... ORDER BY</>.
+    It is possible for a <command>SELECT</> command using <literal>ORDER
+    BY</literal> and <literal>FOR UPDATE/SHARE</literal> to return rows out of
+    order.  This is because <literal>ORDER BY</> is applied first.
+    The command sorts the result, but might then block trying to obtain a lock
+    on one or more of the rows.  Once the <literal>SELECT</> unblocks, some
+    of the ordering column values might have been modified, leading to those
+    rows appearing to be out of order (though they are in order in terms
+    of the original column values).  This can be worked around at need by
+    placing the <literal>FOR UPDATE/SHARE</literal> clause in a sub-query,
+    for example
+<programlisting>
+SELECT * FROM (SELECT * FROM mytable FOR UPDATE) ss ORDER BY column1;
+</programlisting>
+    Note that this will result in locking all rows of <structname>mytable</>,
+    whereas <literal>FOR UPDATE</> at the top level would lock only the
+    actually returned rows.  This can make for a significant performance
+    difference, particularly if the <literal>ORDER BY</> is combined with
+    <literal>LIMIT</> or other restrictions.  So this technique is recommended
+    only if concurrent updates of the ordering columns are expected and a
+    strictly sorted result is required.
    </para>
   </caution>
   </refsect2>
@@ -1541,15 +1570,28 @@ SELECT distributors.* WHERE distributors.name = 'Westward';
     used by <productname>MySQL</productname>.  The SQL:2008 standard
     has introduced the clauses <literal>OFFSET ... FETCH {FIRST|NEXT}
     ...</literal> for the same functionality, as shown above
-    in <xref linkend="sql-limit" endterm="sql-limit-title">, and this
+    in <xref linkend="sql-limit" endterm="sql-limit-title">.  This
     syntax is also used by <productname>IBM DB2</productname>.
     (Applications written for <productname>Oracle</productname>
     frequently use a workaround involving the automatically
-    generated <literal>rownum</literal> column, not available in
+    generated <literal>rownum</literal> column, which is not available in
     PostgreSQL, to implement the effects of these clauses.)
    </para>
   </refsect2>
 
+  <refsect2>
+   <title><literal>FOR UPDATE</> and <literal>FOR SHARE</></title>
+
+   <para>
+    Although <literal>FOR UPDATE</> appears in the SQL standard, the
+    standard allows it only as an option of <command>DECLARE CURSOR</>.
+    <productname>PostgreSQL</productname> allows it in any <command>SELECT</>
+    query as well as in sub-<command>SELECT</>s, but this is an extension.
+    The <literal>FOR SHARE</> variant, and the <literal>NOWAIT</> option,
+    do not appear in the standard.
+   </para>
+  </refsect2>
+
   <refsect2>
    <title>Nonstandard Clauses</title>
 
index deee99941708b0eb476d3a8b43f22adee9025389..a9efce40532a8a92ba5ef9c7a91be932ad8f60c3 100644 (file)
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.449 2009/10/26 02:26:31 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.450 2009/10/28 14:55:38 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1859,6 +1859,7 @@ _copyRowMarkClause(RowMarkClause *from)
        COPY_SCALAR_FIELD(rti);
        COPY_SCALAR_FIELD(forUpdate);
        COPY_SCALAR_FIELD(noWait);
+       COPY_SCALAR_FIELD(pushedDown);
 
        return newnode;
 }
@@ -2223,6 +2224,7 @@ _copyQuery(Query *from)
        COPY_SCALAR_FIELD(hasSubLinks);
        COPY_SCALAR_FIELD(hasDistinctOn);
        COPY_SCALAR_FIELD(hasRecursive);
+       COPY_SCALAR_FIELD(hasForUpdate);
        COPY_NODE_FIELD(cteList);
        COPY_NODE_FIELD(rtable);
        COPY_NODE_FIELD(jointree);
index f7e9547a1f8b091070231429e6f0fad609593224..d60d238be93e2f33ea8a078922a3472da3d7be3b 100644 (file)
@@ -22,7 +22,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.371 2009/10/26 02:26:31 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.372 2009/10/28 14:55:38 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -860,6 +860,7 @@ _equalQuery(Query *a, Query *b)
        COMPARE_SCALAR_FIELD(hasSubLinks);
        COMPARE_SCALAR_FIELD(hasDistinctOn);
        COMPARE_SCALAR_FIELD(hasRecursive);
+       COMPARE_SCALAR_FIELD(hasForUpdate);
        COMPARE_NODE_FIELD(cteList);
        COMPARE_NODE_FIELD(rtable);
        COMPARE_NODE_FIELD(jointree);
@@ -2198,6 +2199,7 @@ _equalRowMarkClause(RowMarkClause *a, RowMarkClause *b)
        COMPARE_SCALAR_FIELD(rti);
        COMPARE_SCALAR_FIELD(forUpdate);
        COMPARE_SCALAR_FIELD(noWait);
+       COMPARE_SCALAR_FIELD(pushedDown);
 
        return true;
 }
index ae7a859bd5517bf31aba791d6dd44d2bef0501f0..7abee7a2d84903239a6c891fefcd507680bdb221 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.370 2009/10/26 02:26:31 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.371 2009/10/28 14:55:38 tgl Exp $
  *
  * NOTES
  *       Every node type that can appear in stored rules' parsetrees *must*
@@ -1987,6 +1987,7 @@ _outQuery(StringInfo str, Query *node)
        WRITE_BOOL_FIELD(hasSubLinks);
        WRITE_BOOL_FIELD(hasDistinctOn);
        WRITE_BOOL_FIELD(hasRecursive);
+       WRITE_BOOL_FIELD(hasForUpdate);
        WRITE_NODE_FIELD(cteList);
        WRITE_NODE_FIELD(rtable);
        WRITE_NODE_FIELD(jointree);
@@ -2036,6 +2037,7 @@ _outRowMarkClause(StringInfo str, RowMarkClause *node)
        WRITE_UINT_FIELD(rti);
        WRITE_BOOL_FIELD(forUpdate);
        WRITE_BOOL_FIELD(noWait);
+       WRITE_BOOL_FIELD(pushedDown);
 }
 
 static void
index a1520276e22eaad4b661e183448214f69a5bb9c5..4192ca7e330e29ebf0b6324ca66d63bd5ee262a0 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.226 2009/10/26 02:26:32 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.227 2009/10/28 14:55:38 tgl Exp $
  *
  * NOTES
  *       Path and Plan nodes do not have any readfuncs support, because we
@@ -203,6 +203,7 @@ _readQuery(void)
        READ_BOOL_FIELD(hasSubLinks);
        READ_BOOL_FIELD(hasDistinctOn);
        READ_BOOL_FIELD(hasRecursive);
+       READ_BOOL_FIELD(hasForUpdate);
        READ_NODE_FIELD(cteList);
        READ_NODE_FIELD(rtable);
        READ_NODE_FIELD(jointree);
@@ -295,6 +296,7 @@ _readRowMarkClause(void)
        READ_UINT_FIELD(rti);
        READ_BOOL_FIELD(forUpdate);
        READ_BOOL_FIELD(noWait);
+       READ_BOOL_FIELD(pushedDown);
 
        READ_DONE();
 }
index 0b396b29bcc04cc4370fb8324eaa5fad45514bcb..6b24098cdd3e3a431129af905b133f3422f7c349 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.260 2009/10/26 02:26:33 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.261 2009/10/28 14:55:38 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1638,19 +1638,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
        }
 
        /*
-        * If there is a LIMIT/OFFSET clause, add the LIMIT node.
-        */
-       if (parse->limitCount || parse->limitOffset)
-       {
-               result_plan = (Plan *) make_limit(result_plan,
-                                                                                 parse->limitOffset,
-                                                                                 parse->limitCount,
-                                                                                 offset_est,
-                                                                                 count_est);
-       }
-
-       /*
-        * Finally, if there is a FOR UPDATE/SHARE clause, add the LockRows node.
+        * If there is a FOR UPDATE/SHARE clause, add the LockRows node.
         * (Note: we intentionally test parse->rowMarks not root->rowMarks here.
         * If there are only non-locking rowmarks, they should be handled by
         * the ModifyTable node instead.)
@@ -1660,6 +1648,23 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
                result_plan = (Plan *) make_lockrows(result_plan,
                                                                                         root->rowMarks,
                                                                                         SS_assign_special_param(root));
+               /*
+                * The result can no longer be assumed sorted, since locking might
+                * cause the sort key columns to be replaced with new values.
+                */
+               current_pathkeys = NIL;
+       }
+
+       /*
+        * Finally, if there is a LIMIT/OFFSET clause, add the LIMIT node.
+        */
+       if (parse->limitCount || parse->limitOffset)
+       {
+               result_plan = (Plan *) make_limit(result_plan,
+                                                                                 parse->limitOffset,
+                                                                                 parse->limitCount,
+                                                                                 offset_est,
+                                                                                 count_est);
        }
 
        /* Compute result-relations list if needed */
@@ -1795,20 +1800,33 @@ preprocess_rowmarks(PlannerInfo *root)
 
        /*
         * Convert RowMarkClauses to PlanRowMark representation.
-        *
-        * Note: currently, it is syntactically impossible to have FOR UPDATE
-        * applied to an update/delete target rel.  If that ever becomes
-        * possible, we should drop the target from the PlanRowMark list.
         */
        prowmarks = NIL;
        foreach(l, parse->rowMarks)
        {
                RowMarkClause *rc = (RowMarkClause *) lfirst(l);
-               PlanRowMark *newrc = makeNode(PlanRowMark);
+               RangeTblEntry *rte = rt_fetch(rc->rti, parse->rtable);
+               PlanRowMark *newrc;
 
+               /*
+                * Currently, it is syntactically impossible to have FOR UPDATE
+                * applied to an update/delete target rel.  If that ever becomes
+                * possible, we should drop the target from the PlanRowMark list.
+                */
                Assert(rc->rti != parse->resultRelation);
+
+               /*
+                * Ignore RowMarkClauses for subqueries; they aren't real tables
+                * and can't support true locking.  Subqueries that got flattened
+                * into the main query should be ignored completely.  Any that didn't
+                * will get ROW_MARK_COPY items in the next loop.
+                */
+               if (rte->rtekind != RTE_RELATION)
+                       continue;
+
                rels = bms_del_member(rels, rc->rti);
 
+               newrc = makeNode(PlanRowMark);
                newrc->rti = newrc->prti = rc->rti;
                if (rc->forUpdate)
                        newrc->markType = ROW_MARK_EXCLUSIVE;
@@ -1838,7 +1856,6 @@ preprocess_rowmarks(PlannerInfo *root)
                        continue;
 
                newrc = makeNode(PlanRowMark);
-
                newrc->rti = newrc->prti = i;
                /* real tables support REFERENCE, anything else needs COPY */
                if (rte->rtekind == RTE_RELATION)
index f48bd31151cdcf2b6f7807e26d556d252b3e5370..7d4d7b217f7e04b4d8470d7571819815617716a2 100644 (file)
@@ -16,7 +16,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.68 2009/10/26 02:26:35 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.69 2009/10/28 14:55:38 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1030,6 +1030,12 @@ is_simple_subquery(Query *subquery)
        /*
         * Can't pull up a subquery involving grouping, aggregation, sorting,
         * limiting, or WITH.  (XXX WITH could possibly be allowed later)
+        *
+        * We also don't pull up a subquery that has explicit FOR UPDATE/SHARE
+        * clauses, because pullup would cause the locking to occur semantically
+        * higher than it should.  Implicit FOR UPDATE/SHARE is okay because
+        * in that case the locking was originally declared in the upper query
+        * anyway.
         */
        if (subquery->hasAggs ||
                subquery->hasWindowFuncs ||
@@ -1039,6 +1045,7 @@ is_simple_subquery(Query *subquery)
                subquery->distinctClause ||
                subquery->limitOffset ||
                subquery->limitCount ||
+               subquery->hasForUpdate ||
                subquery->cteList)
                return false;
 
index 84cbd2142e338991b3e2823c4da7c42e6c66fef4..c89c7c82a08b0b33141a58703797a4a8a1d9ff61 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.394 2009/10/27 17:11:18 tgl Exp $
+ *     $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.395 2009/10/28 14:55:43 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -60,8 +60,8 @@ static Query *transformDeclareCursorStmt(ParseState *pstate,
                                                   DeclareCursorStmt *stmt);
 static Query *transformExplainStmt(ParseState *pstate,
                                         ExplainStmt *stmt);
-static void transformLockingClause(ParseState *pstate,
-                                          Query *qry, LockingClause *lc);
+static void transformLockingClause(ParseState *pstate, Query *qry,
+                                                                  LockingClause *lc, bool pushedDown);
 static bool check_parameter_resolution_walker(Node *node, ParseState *pstate);
 
 
@@ -896,7 +896,8 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 
        foreach(l, stmt->lockingClause)
        {
-               transformLockingClause(pstate, qry, (LockingClause *) lfirst(l));
+               transformLockingClause(pstate, qry,
+                                                          (LockingClause *) lfirst(l), false);
        }
 
        return qry;
@@ -1348,7 +1349,8 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 
        foreach(l, lockingClause)
        {
-               transformLockingClause(pstate, qry, (LockingClause *) lfirst(l));
+               transformLockingClause(pstate, qry,
+                                                          (LockingClause *) lfirst(l), false);
        }
 
        return qry;
@@ -2056,7 +2058,8 @@ CheckSelectLocking(Query *qry)
  * in rewriteHandler.c, and isLockedRefname() in parse_relation.c.
  */
 static void
-transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc)
+transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
+                                          bool pushedDown)
 {
        List       *lockedRels = lc->lockedRels;
        ListCell   *l;
@@ -2084,16 +2087,22 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc)
                        switch (rte->rtekind)
                        {
                                case RTE_RELATION:
-                                       applyLockingClause(qry, i, lc->forUpdate, lc->noWait);
+                                       applyLockingClause(qry, i,
+                                                                          lc->forUpdate, lc->noWait, pushedDown);
                                        rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
                                        break;
                                case RTE_SUBQUERY:
-
+                                       applyLockingClause(qry, i,
+                                                                          lc->forUpdate, lc->noWait, pushedDown);
                                        /*
                                         * FOR UPDATE/SHARE of subquery is propagated to all of
-                                        * subquery's rels
+                                        * subquery's rels, too.  We could do this later (based
+                                        * on the marking of the subquery RTE) but it is convenient
+                                        * to have local knowledge in each query level about
+                                        * which rels need to be opened with RowShareLock.
                                         */
-                                       transformLockingClause(pstate, rte->subquery, allrels);
+                                       transformLockingClause(pstate, rte->subquery,
+                                                                                  allrels, true);
                                        break;
                                default:
                                        /* ignore JOIN, SPECIAL, FUNCTION, VALUES, CTE RTEs */
@@ -2127,16 +2136,17 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc)
                                        {
                                                case RTE_RELATION:
                                                        applyLockingClause(qry, i,
-                                                                                          lc->forUpdate, lc->noWait);
+                                                                                          lc->forUpdate, lc->noWait,
+                                                                                          pushedDown);
                                                        rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
                                                        break;
                                                case RTE_SUBQUERY:
-
-                                                       /*
-                                                        * FOR UPDATE/SHARE of subquery is propagated to
-                                                        * all of subquery's rels
-                                                        */
-                                                       transformLockingClause(pstate, rte->subquery, allrels);
+                                                       applyLockingClause(qry, i,
+                                                                                          lc->forUpdate, lc->noWait,
+                                                                                          pushedDown);
+                                                       /* see comment above */
+                                                       transformLockingClause(pstate, rte->subquery,
+                                                                                                  allrels, true);
                                                        break;
                                                case RTE_JOIN:
                                                        ereport(ERROR,
@@ -2190,10 +2200,15 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc)
  * Record locking info for a single rangetable item
  */
 void
-applyLockingClause(Query *qry, Index rtindex, bool forUpdate, bool noWait)
+applyLockingClause(Query *qry, Index rtindex,
+                                  bool forUpdate, bool noWait, bool pushedDown)
 {
        RowMarkClause *rc;
 
+       /* If it's an explicit clause, make sure hasForUpdate gets set */
+       if (!pushedDown)
+               qry->hasForUpdate = true;
+
        /* Check for pre-existing entry for same rtindex */
        if ((rc = get_parse_rowmark(qry, rtindex)) != NULL)
        {
@@ -2207,9 +2222,12 @@ applyLockingClause(Query *qry, Index rtindex, bool forUpdate, bool noWait)
                 * is a bit more debatable but raising an error doesn't seem helpful.
                 * (Consider for instance SELECT FOR UPDATE NOWAIT from a view that
                 * internally contains a plain FOR UPDATE spec.)
+                *
+                * And of course pushedDown becomes false if any clause is explicit.
                 */
                rc->forUpdate |= forUpdate;
                rc->noWait |= noWait;
+               rc->pushedDown &= pushedDown;
                return;
        }
 
@@ -2218,6 +2236,7 @@ applyLockingClause(Query *qry, Index rtindex, bool forUpdate, bool noWait)
        rc->rti = rtindex;
        rc->forUpdate = forUpdate;
        rc->noWait = noWait;
+       rc->pushedDown = pushedDown;
        qry->rowMarks = lappend(qry->rowMarks, rc);
 }
 
index 0ea80e5e8bc80ce94a1fc045ed86b8fd23a65358..8f5b3002278d1b36ec89b7444ec1d59666582e51 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.189 2009/10/27 17:11:18 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.190 2009/10/28 14:55:43 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -52,7 +52,7 @@ static Node *get_assignment_input(Node *node);
 static void rewriteValuesRTE(RangeTblEntry *rte, Relation target_relation,
                                 List *attrnos);
 static void markQueryForLocking(Query *qry, Node *jtnode,
-                                       bool forUpdate, bool noWait);
+                                       bool forUpdate, bool noWait, bool pushedDown);
 static List *matchLocks(CmdType event, RuleLock *rulelocks,
                   int varno, Query *parsetree);
 static Query *fireRIRrules(Query *parsetree, List *activeRIRs);
@@ -1189,23 +1189,13 @@ ApplyRetrieveRule(Query *parsetree,
        rte->modifiedCols = NULL;
 
        /*
-        * FOR UPDATE/SHARE of view?
+        * If FOR UPDATE/SHARE of view, mark all the contained tables as
+        * implicit FOR UPDATE/SHARE, the same as the parser would have done
+        * if the view's subquery had been written out explicitly.
         */
        if ((rc = get_parse_rowmark(parsetree, rt_index)) != NULL)
-       {
-               /*
-                * Remove the view from the list of rels that will actually be marked
-                * FOR UPDATE/SHARE by the executor.  It will still be access-checked
-                * for write access, though.
-                */
-               parsetree->rowMarks = list_delete_ptr(parsetree->rowMarks, rc);
-
-               /*
-                * Set up the view's referenced tables as if FOR UPDATE/SHARE.
-                */
                markQueryForLocking(rule_action, (Node *) rule_action->jointree,
-                                                       rc->forUpdate, rc->noWait);
-       }
+                                                       rc->forUpdate, rc->noWait, true);
 
        return parsetree;
 }
@@ -1222,7 +1212,8 @@ ApplyRetrieveRule(Query *parsetree,
  * to scan the jointree to determine which rels are used.
  */
 static void
-markQueryForLocking(Query *qry, Node *jtnode, bool forUpdate, bool noWait)
+markQueryForLocking(Query *qry, Node *jtnode,
+                                       bool forUpdate, bool noWait, bool pushedDown)
 {
        if (jtnode == NULL)
                return;
@@ -1233,14 +1224,15 @@ markQueryForLocking(Query *qry, Node *jtnode, bool forUpdate, bool noWait)
 
                if (rte->rtekind == RTE_RELATION)
                {
-                       applyLockingClause(qry, rti, forUpdate, noWait);
+                       applyLockingClause(qry, rti, forUpdate, noWait, pushedDown);
                        rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
                }
                else if (rte->rtekind == RTE_SUBQUERY)
                {
+                       applyLockingClause(qry, rti, forUpdate, noWait, pushedDown);
                        /* FOR UPDATE/SHARE of subquery is propagated to subquery's rels */
                        markQueryForLocking(rte->subquery, (Node *) rte->subquery->jointree,
-                                                               forUpdate, noWait);
+                                                               forUpdate, noWait, true);
                }
                /* other RTE types are unaffected by FOR UPDATE */
        }
@@ -1250,14 +1242,14 @@ markQueryForLocking(Query *qry, Node *jtnode, bool forUpdate, bool noWait)
                ListCell   *l;
 
                foreach(l, f->fromlist)
-                       markQueryForLocking(qry, lfirst(l), forUpdate, noWait);
+                       markQueryForLocking(qry, lfirst(l), forUpdate, noWait, pushedDown);
        }
        else if (IsA(jtnode, JoinExpr))
        {
                JoinExpr   *j = (JoinExpr *) jtnode;
 
-               markQueryForLocking(qry, j->larg, forUpdate, noWait);
-               markQueryForLocking(qry, j->rarg, forUpdate, noWait);
+               markQueryForLocking(qry, j->larg, forUpdate, noWait, pushedDown);
+               markQueryForLocking(qry, j->rarg, forUpdate, noWait, pushedDown);
        }
        else
                elog(ERROR, "unrecognized node type: %d",
index 8538c74d1231e0a1029c27303167e80a3dcecf7f..85e52293e76b8f8aa1bc799cc7740f27a338f82a 100644 (file)
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.310 2009/10/14 22:14:23 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.311 2009/10/28 14:55:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2549,21 +2549,28 @@ get_select_query_def(Query *query, deparse_context *context,
        }
 
        /* Add FOR UPDATE/SHARE clauses if present */
-       foreach(l, query->rowMarks)
+       if (query->hasForUpdate)
        {
-               RowMarkClause *rc = (RowMarkClause *) lfirst(l);
-               RangeTblEntry *rte = rt_fetch(rc->rti, query->rtable);
+               foreach(l, query->rowMarks)
+               {
+                       RowMarkClause *rc = (RowMarkClause *) lfirst(l);
+                       RangeTblEntry *rte = rt_fetch(rc->rti, query->rtable);
 
-               if (rc->forUpdate)
-                       appendContextKeyword(context, " FOR UPDATE",
-                                                                -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
-               else
-                       appendContextKeyword(context, " FOR SHARE",
-                                                                -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
-               appendStringInfo(buf, " OF %s",
-                                                quote_identifier(rte->eref->aliasname));
-               if (rc->noWait)
-                       appendStringInfo(buf, " NOWAIT");
+                       /* don't print implicit clauses */
+                       if (rc->pushedDown)
+                               continue;
+
+                       if (rc->forUpdate)
+                               appendContextKeyword(context, " FOR UPDATE",
+                                                                        -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
+                       else
+                               appendContextKeyword(context, " FOR SHARE",
+                                                                        -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
+                       appendStringInfo(buf, " OF %s",
+                                                        quote_identifier(rte->eref->aliasname));
+                       if (rc->noWait)
+                               appendStringInfo(buf, " NOWAIT");
+               }
        }
 
        context->windowClause = save_windowclause;
index aa9348a336ff2f081e56bc4faca786a4f571398a..74bf01d29c5d3ddfc347d2a33618a9a42090b89a 100644 (file)
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.547 2009/10/26 02:26:41 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.548 2009/10/28 14:55:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     200910251
+#define CATALOG_VERSION_NO     200910281
 
 #endif
index 450a89fe85b47c43b884089e4e2b87515b70ece0..2078526092beb823e93576ab3db89caf58daf617 100644 (file)
@@ -13,7 +13,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.411 2009/10/26 02:26:41 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.412 2009/10/28 14:55:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -118,6 +118,7 @@ typedef struct Query
        bool            hasSubLinks;    /* has subquery SubLink */
        bool            hasDistinctOn;  /* distinctClause is from DISTINCT ON */
        bool            hasRecursive;   /* WITH RECURSIVE was specified */
+       bool            hasForUpdate;   /* FOR UPDATE or FOR SHARE was specified */
 
        List       *cteList;            /* WITH list (of CommonTableExpr's) */
 
@@ -803,7 +804,12 @@ typedef struct WindowClause
  *        parser output representation of FOR UPDATE/SHARE clauses
  *
  * Query.rowMarks contains a separate RowMarkClause node for each relation
- * identified as a FOR UPDATE/SHARE target.
+ * identified as a FOR UPDATE/SHARE target.  If FOR UPDATE/SHARE is applied
+ * to a subquery, we generate RowMarkClauses for all normal and subquery rels
+ * in the subquery, but they are marked pushedDown = true to distinguish them
+ * from clauses that were explicitly written at this query level.  Also,
+ * Query.hasForUpdate tells whether there were explicit FOR UPDATE/SHARE
+ * clauses in the current query level.
  */
 typedef struct RowMarkClause
 {
@@ -811,6 +817,7 @@ typedef struct RowMarkClause
        Index           rti;                    /* range table index of target relation */
        bool            forUpdate;              /* true = FOR UPDATE, false = FOR SHARE */
        bool            noWait;                 /* NOWAIT option */
+       bool            pushedDown;             /* pushed down from higher query level? */
 } RowMarkClause;
 
 /*
index 59e64c91455f75485c55716e242d59b30a09246c..8acd6153f8a76c7ed256d4ad48e52c1be3fc0668 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.42 2009/10/27 17:11:18 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/parser/analyze.h,v 1.43 2009/10/28 14:55:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -31,6 +31,6 @@ extern bool analyze_requires_snapshot(Node *parseTree);
 
 extern void CheckSelectLocking(Query *qry);
 extern void applyLockingClause(Query *qry, Index rtindex,
-                                  bool forUpdate, bool noWait);
+                                  bool forUpdate, bool noWait, bool pushedDown);
 
 #endif   /* ANALYZE_H */