Implement SKIP LOCKED for row-level locks
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Tue, 7 Oct 2014 20:23:34 +0000 (17:23 -0300)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Tue, 7 Oct 2014 20:23:34 +0000 (17:23 -0300)
This clause changes the behavior of SELECT locking clauses in the
presence of locked rows: instead of causing a process to block waiting
for the locks held by other processes (or raise an error, with NOWAIT),
SKIP LOCKED makes the new reader skip over such rows.  While this is not
appropriate behavior for general purposes, there are some cases in which
it is useful, such as queue-like tables.

Catalog version bumped because this patch changes the representation of
stored rules.

Reviewed by Craig Ringer (based on a previous attempt at an
implementation by Simon Riggs, who also provided input on the syntax
used in the current patch), David Rowley, and Álvaro Herrera.

Author: Thomas Munro

38 files changed:
doc/src/sgml/ref/select.sgml
doc/src/sgml/sql.sgml
src/backend/access/heap/heapam.c
src/backend/commands/trigger.c
src/backend/executor/execMain.c
src/backend/executor/nodeLockRows.c
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/prepsecurity.c
src/backend/optimizer/prep/prepunion.c
src/backend/parser/analyze.c
src/backend/parser/gram.y
src/backend/rewrite/rewriteHandler.c
src/backend/utils/adt/ruleutils.c
src/include/access/heapam.h
src/include/catalog/catversion.h
src/include/executor/executor.h
src/include/nodes/execnodes.h
src/include/nodes/parsenodes.h
src/include/nodes/plannodes.h
src/include/parser/analyze.h
src/include/parser/kwlist.h
src/include/utils/lockwaitpolicy.h [new file with mode: 0644]
src/include/utils/snapshot.h
src/test/isolation/expected/skip-locked-2.out [new file with mode: 0644]
src/test/isolation/expected/skip-locked-3.out [new file with mode: 0644]
src/test/isolation/expected/skip-locked-4.out [new file with mode: 0644]
src/test/isolation/expected/skip-locked-4_1.out [new file with mode: 0644]
src/test/isolation/expected/skip-locked.out [new file with mode: 0644]
src/test/isolation/isolation_schedule
src/test/isolation/specs/nowait-4.spec
src/test/isolation/specs/skip-locked-2.spec [new file with mode: 0644]
src/test/isolation/specs/skip-locked-3.spec [new file with mode: 0644]
src/test/isolation/specs/skip-locked-4.spec [new file with mode: 0644]
src/test/isolation/specs/skip-locked.spec [new file with mode: 0644]

index 940d1aa5c0dd66b11bdc9f9c56c9e16e16d99de2..473939ab4eb1c1db9d055365f6f7914565fd5652 100644 (file)
@@ -45,7 +45,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
     [ LIMIT { <replaceable class="parameter">count</replaceable> | ALL } ]
     [ OFFSET <replaceable class="parameter">start</replaceable> [ ROW | ROWS ] ]
     [ FETCH { FIRST | NEXT } [ <replaceable class="parameter">count</replaceable> ] { ROW | ROWS } ONLY ]
-    [ FOR { UPDATE | NO KEY UPDATE | SHARE | KEY SHARE } [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ] [ NOWAIT ] [...] ]
+    [ FOR { UPDATE | NO KEY UPDATE | SHARE | KEY SHARE } [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ] [ NOWAIT | SKIP LOCKED ] [...] ]
 
 <phrase>where <replaceable class="parameter">from_item</replaceable> can be one of:</phrase>
 
@@ -1284,7 +1284,7 @@ FETCH { FIRST | NEXT } [ <replaceable class="parameter">count</replaceable> ] {
     The locking clause has the general form
 
 <synopsis>
-FOR <replaceable>lock_strength</> [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ] [ NOWAIT ]
+FOR <replaceable>lock_strength</> [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ] [ NOWAIT | SKIP LOCKED ]
 </synopsis>
 
     where <replaceable>lock_strength</> can be one of
@@ -1360,9 +1360,15 @@ KEY SHARE
 
    <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
+    use either the <literal>NOWAIT</> or <literal>SKIP LOCKED</literal>
+    option.  With <literal>NOWAIT</>, the statement reports an error, rather
+    than waiting, if a selected row cannot be locked immediately.
+    With <literal>SKIP LOCKED</literal>, any selected rows that cannot be
+    immediately locked are skipped.  Skipping locked rows provides an
+    inconsistent view of the data, so this is not suitable for general purpose
+    work, but can be used to avoid lock contention with multiple consumers
+    accessing a queue-like table.
+    Note that <literal>NOWAIT</> and <literal>SKIP LOCKED</literal> apply 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
@@ -1394,7 +1400,9 @@ KEY SHARE
     then it is processed as if it was only specified by the strongest one.
     Similarly, a table is processed
     as <literal>NOWAIT</> if that is specified in any of the clauses
-    affecting it.
+    affecting it.  Otherwise, it is processed
+    as <literal>SKIP LOCKED</literal> if that is specified in any of the
+    clauses affecting it.
    </para>
 
    <para>
@@ -1931,9 +1939,9 @@ SELECT distributors.* WHERE distributors.name = 'Westward';
     <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 NO KEY UPDATE</>, <literal>FOR SHARE</> and
-    <literal>FOR KEY SHARE</> variants,
-    as well as the <literal>NOWAIT</> option,
-    do not appear in the standard.
+    <literal>FOR KEY SHARE</> variants, as well as the <literal>NOWAIT</>
+    and <literal>SKIP LOCKED</literal> options, do not appear in the
+    standard.
    </para>
   </refsect2>
 
index ba92607966c33700d84b82ce94dcb80dcec19aa9..57396d7c24596b2b52f2402c281945052c1a6d7a 100644 (file)
@@ -863,7 +863,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="PARAMETER">expression</replac
     [ ORDER BY <replaceable class="parameter">expression</replaceable> [ ASC | DESC | USING <replaceable class="parameter">operator</replaceable> ] [ NULLS { FIRST | LAST } ] [, ...] ]
     [ LIMIT { <replaceable class="PARAMETER">count</replaceable> | ALL } ]
     [ OFFSET <replaceable class="PARAMETER">start</replaceable> ]
-    [ FOR { UPDATE | SHARE } [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ] [ NOWAIT ] [...] ]
+    [ FOR { UPDATE | SHARE } [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ] [ NOWAIT | SKIP LOCKED ] [...] ]
 </synopsis>
     </para>
 
index ae15c0b95569693828ceb9cc1fee995db30ac7e9..808b942c5e8c778aba3c6727e2581be6895bd32b 100644 (file)
@@ -4090,7 +4090,7 @@ get_mxact_status_for_lock(LockTupleMode mode, bool is_update)
  *     cid: current command ID (used for visibility test, and stored into
  *             tuple's cmax if lock is successful)
  *     mode: indicates if shared or exclusive tuple lock is desired
- *     nowait: if true, ereport rather than blocking if lock not available
+ *     wait_policy: what to do if tuple lock is not available
  *     follow_updates: if true, follow the update chain to also lock descendant
  *             tuples.
  *
@@ -4103,6 +4103,7 @@ get_mxact_status_for_lock(LockTupleMode mode, bool is_update)
  *     HeapTupleMayBeUpdated: lock was successfully acquired
  *     HeapTupleSelfUpdated: lock failed because tuple updated by self
  *     HeapTupleUpdated: lock failed because tuple updated by other xact
+ *     HeapTupleWouldBlock: lock couldn't be acquired and wait_policy is skip
  *
  * In the failure cases, the routine fills *hufd with the tuple's t_ctid,
  * t_xmax (resolving a possible MultiXact, if necessary), and t_cmax
@@ -4114,7 +4115,7 @@ get_mxact_status_for_lock(LockTupleMode mode, bool is_update)
  */
 HTSU_Result
 heap_lock_tuple(Relation relation, HeapTuple tuple,
-                               CommandId cid, LockTupleMode mode, bool nowait,
+                               CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
                                bool follow_updates,
                                Buffer *buffer, HeapUpdateFailureData *hufd)
 {
@@ -4220,16 +4221,28 @@ l3:
                 */
                if (!have_tuple_lock)
                {
-                       if (nowait)
+                       switch (wait_policy)
                        {
-                               if (!ConditionalLockTupleTuplock(relation, tid, mode))
-                                       ereport(ERROR,
-                                                       (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-                                       errmsg("could not obtain lock on row in relation \"%s\"",
-                                                  RelationGetRelationName(relation))));
+                               case LockWaitBlock:
+                                       LockTupleTuplock(relation, tid, mode);
+                                       break;
+                               case LockWaitSkip:
+                                       if (!ConditionalLockTupleTuplock(relation, tid, mode))
+                                       {
+                                               result = HeapTupleWouldBlock;
+                                               /* recovery code expects to have buffer lock held */
+                                               LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+                                               goto failed;
+                                       }
+                                       break;
+                               case LockWaitError:
+                                       if (!ConditionalLockTupleTuplock(relation, tid, mode))
+                                               ereport(ERROR,
+                                                               (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+                                                                errmsg("could not obtain lock on row in relation \"%s\"",
+                                                                               RelationGetRelationName(relation))));
+                                       break;
                        }
-                       else
-                               LockTupleTuplock(relation, tid, mode);
                        have_tuple_lock = true;
                }
 
@@ -4432,21 +4445,35 @@ l3:
                                if (status >= MultiXactStatusNoKeyUpdate)
                                        elog(ERROR, "invalid lock mode in heap_lock_tuple");
 
-                               /* wait for multixact to end */
-                               if (nowait)
+                               /* wait for multixact to end, or die trying  */
+                               switch (wait_policy)
                                {
-                                       if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
-                                                                                                 status, infomask, relation,
-                                                                                                       NULL))
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-                                                                errmsg("could not obtain lock on row in relation \"%s\"",
-                                                                               RelationGetRelationName(relation))));
+                                       case LockWaitBlock:
+                                               MultiXactIdWait((MultiXactId) xwait, status, infomask,
+                                                                               relation, &tuple->t_data->t_ctid, XLTW_Lock, NULL);
+                                               break;
+                                       case LockWaitSkip:
+                                               if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
+                                                                                                               status, infomask, relation,
+                                                                                                               NULL))
+                                               {
+                                                       result = HeapTupleWouldBlock;
+                                                       /* recovery code expects to have buffer lock held */
+                                                       LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+                                                       goto failed;
+                                               }
+                                               break;
+                                       case LockWaitError:
+                                               if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
+                                                                                                               status, infomask, relation,
+                                                                                                               NULL))
+                                                       ereport(ERROR,
+                                                                       (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+                                                                        errmsg("could not obtain lock on row in relation \"%s\"",
+                                                                                       RelationGetRelationName(relation))));
+
+                                               break;
                                }
-                               else
-                                       MultiXactIdWait((MultiXactId) xwait, status, infomask,
-                                                                       relation, &tuple->t_data->t_ctid,
-                                                                       XLTW_Lock, NULL);
 
                                /* if there are updates, follow the update chain */
                                if (follow_updates &&
@@ -4491,18 +4518,30 @@ l3:
                        }
                        else
                        {
-                               /* wait for regular transaction to end */
-                               if (nowait)
+                               /* wait for regular transaction to end, or die trying */
+                               switch (wait_policy)
                                {
-                                       if (!ConditionalXactLockTableWait(xwait))
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-                                                                errmsg("could not obtain lock on row in relation \"%s\"",
-                                                                               RelationGetRelationName(relation))));
+                                       case LockWaitBlock:
+                                               XactLockTableWait(xwait, relation, &tuple->t_data->t_ctid,
+                                                                                 XLTW_Lock);
+                                               break;
+                                       case LockWaitSkip:
+                                               if (!ConditionalXactLockTableWait(xwait))
+                                               {
+                                                       result = HeapTupleWouldBlock;
+                                                       /* recovery code expects to have buffer lock held */
+                                                       LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+                                                       goto failed;
+                                               }
+                                               break;
+                                       case LockWaitError:
+                                               if (!ConditionalXactLockTableWait(xwait))
+                                                       ereport(ERROR,
+                                                                       (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+                                                                        errmsg("could not obtain lock on row in relation \"%s\"",
+                                                                                       RelationGetRelationName(relation))));
+                                               break;
                                }
-                               else
-                                       XactLockTableWait(xwait, relation, &tuple->t_data->t_ctid,
-                                                                         XLTW_Lock);
 
                                /* if there are updates, follow the update chain */
                                if (follow_updates &&
@@ -4564,7 +4603,8 @@ l3:
 failed:
        if (result != HeapTupleMayBeUpdated)
        {
-               Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated);
+               Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated ||
+                          result == HeapTupleWouldBlock);
                Assert(!(tuple->t_data->t_infomask & HEAP_XMAX_INVALID));
                hufd->ctid = tuple->t_data->t_ctid;
                hufd->xmax = HeapTupleHeaderGetUpdateXid(tuple->t_data);
index 9bf0098b6cbb81bc22b031802d473a3b6989f5bf..f4c0ffa0211ca9fc64a858cbb79ed0c1fc3d7123 100644 (file)
@@ -2706,7 +2706,7 @@ ltrmark:;
                tuple.t_self = *tid;
                test = heap_lock_tuple(relation, &tuple,
                                                           estate->es_output_cid,
-                                                          lockmode, false /* wait */ ,
+                                                          lockmode, LockWaitBlock,
                                                           false, &buffer, &hufd);
                switch (test)
                {
index a546292da6ebdf46e51437e2d035f9264ab8eee2..a753b2070088fcff5aaf1d8e61b150f56a2ddd34 100644 (file)
@@ -836,7 +836,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
                erm->prti = rc->prti;
                erm->rowmarkId = rc->rowmarkId;
                erm->markType = rc->markType;
-               erm->noWait = rc->noWait;
+               erm->waitPolicy = rc->waitPolicy;
                ItemPointerSetInvalid(&(erm->curCtid));
                estate->es_rowMarks = lappend(estate->es_rowMarks, erm);
        }
@@ -1871,7 +1871,7 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
        /*
         * Get and lock the updated version of the row; if fail, return NULL.
         */
-       copyTuple = EvalPlanQualFetch(estate, relation, lockmode, false /* wait */,
+       copyTuple = EvalPlanQualFetch(estate, relation, lockmode, LockWaitBlock,
                                                                  tid, priorXmax);
 
        if (copyTuple == NULL)
@@ -1930,12 +1930,15 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
  *     estate - executor state data
  *     relation - table containing tuple
  *     lockmode - requested tuple lock mode
- *     noWait - wait mode to pass to heap_lock_tuple
+ *     wait_policy - requested lock wait policy
  *     *tid - t_ctid from the outdated tuple (ie, next updated version)
  *     priorXmax - t_xmax from the outdated tuple
  *
  * Returns a palloc'd copy of the newest tuple version, or NULL if we find
  * that there is no newest version (ie, the row was deleted not updated).
+ * We also return NULL if the tuple is locked and the wait policy is to skip
+ * such tuples.
+ *
  * If successful, we have locked the newest tuple version, so caller does not
  * need to worry about it changing anymore.
  *
@@ -1943,7 +1946,8 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
  * but we use "int" to avoid having to include heapam.h in executor.h.
  */
 HeapTuple
-EvalPlanQualFetch(EState *estate, Relation relation, int lockmode, bool noWait,
+EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
+                                 LockWaitPolicy wait_policy,
                                  ItemPointer tid, TransactionId priorXmax)
 {
        HeapTuple       copyTuple = NULL;
@@ -1992,18 +1996,25 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode, bool noWait,
                        if (TransactionIdIsValid(SnapshotDirty.xmax))
                        {
                                ReleaseBuffer(buffer);
-                               if (noWait)
+                               switch (wait_policy)
                                {
-                                       if (!ConditionalXactLockTableWait(SnapshotDirty.xmax))
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-                                                                errmsg("could not obtain lock on row in relation \"%s\"",
-                                                                               RelationGetRelationName(relation))));
+                                       case LockWaitBlock:
+                                               XactLockTableWait(SnapshotDirty.xmax,
+                                                                                 relation, &tuple.t_data->t_ctid,
+                                                                                 XLTW_FetchUpdated);
+                                               break;
+                                       case LockWaitSkip:
+                                               if (!ConditionalXactLockTableWait(SnapshotDirty.xmax))
+                                                       return NULL; /* skip instead of waiting */
+                                               break;
+                                       case LockWaitError:
+                                               if (!ConditionalXactLockTableWait(SnapshotDirty.xmax))
+                                                       ereport(ERROR,
+                                                                       (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+                                                                        errmsg("could not obtain lock on row in relation \"%s\"",
+                                                                                       RelationGetRelationName(relation))));
+                                               break;
                                }
-                               else
-                                       XactLockTableWait(SnapshotDirty.xmax,
-                                                                         relation, &tuple.t_data->t_ctid,
-                                                                         XLTW_FetchUpdated);
                                continue;               /* loop back to repeat heap_fetch */
                        }
 
@@ -2030,7 +2041,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode, bool noWait,
                         */
                        test = heap_lock_tuple(relation, &tuple,
                                                                   estate->es_output_cid,
-                                                                  lockmode, noWait,
+                                                                  lockmode, wait_policy,
                                                                   false, &buffer, &hufd);
                        /* We now have two pins on the buffer, get rid of one */
                        ReleaseBuffer(buffer);
@@ -2076,6 +2087,10 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode, bool noWait,
                                        /* tuple was deleted, so give up */
                                        return NULL;
 
+                               case HeapTupleWouldBlock:
+                                       ReleaseBuffer(buffer);
+                                       return NULL;
+
                                default:
                                        ReleaseBuffer(buffer);
                                        elog(ERROR, "unrecognized heap_lock_tuple status: %u",
index 814b61efcbaea189cbccf0eed46f3f18a2d89f7b..990240bf0a81113102c4762bb9a8cf73b968b38c 100644 (file)
@@ -133,11 +133,15 @@ lnext:
 
                test = heap_lock_tuple(erm->relation, &tuple,
                                                           estate->es_output_cid,
-                                                          lockmode, erm->noWait, true,
+                                                          lockmode, erm->waitPolicy, true,
                                                           &buffer, &hufd);
                ReleaseBuffer(buffer);
                switch (test)
                {
+                       case HeapTupleWouldBlock:
+                               /* couldn't lock tuple in SKIP LOCKED mode */
+                               goto lnext;
+
                        case HeapTupleSelfUpdated:
 
                                /*
@@ -170,12 +174,15 @@ lnext:
                                }
 
                                /* updated, so fetch and lock the updated version */
-                               copyTuple = EvalPlanQualFetch(estate, erm->relation, lockmode, erm->noWait,
-                                                                                         &hufd.ctid, hufd.xmax);
+                               copyTuple = EvalPlanQualFetch(estate, erm->relation, lockmode,
+                                                                                         erm->waitPolicy, &hufd.ctid, hufd.xmax);
 
                                if (copyTuple == NULL)
                                {
-                                       /* Tuple was deleted, so don't return it */
+                                       /*
+                                        * Tuple was deleted; or it's locked and we're under SKIP
+                                        * LOCKED policy, so don't return it
+                                        */
                                        goto lnext;
                                }
                                /* remember the actually locked tuple's TID */
index 225756c08cc69f874bcb55a1166d600da4e95582..21b070acdaf93eb446d0b0a6e2bc390f99a2ba22 100644 (file)
@@ -959,7 +959,7 @@ _copyPlanRowMark(const PlanRowMark *from)
        COPY_SCALAR_FIELD(prti);
        COPY_SCALAR_FIELD(rowmarkId);
        COPY_SCALAR_FIELD(markType);
-       COPY_SCALAR_FIELD(noWait);
+       COPY_SCALAR_FIELD(waitPolicy);
        COPY_SCALAR_FIELD(isParent);
 
        return newnode;
@@ -2071,7 +2071,7 @@ _copyRowMarkClause(const RowMarkClause *from)
 
        COPY_SCALAR_FIELD(rti);
        COPY_SCALAR_FIELD(strength);
-       COPY_SCALAR_FIELD(noWait);
+       COPY_SCALAR_FIELD(waitPolicy);
        COPY_SCALAR_FIELD(pushedDown);
 
        return newnode;
@@ -2452,7 +2452,7 @@ _copyLockingClause(const LockingClause *from)
 
        COPY_NODE_FIELD(lockedRels);
        COPY_SCALAR_FIELD(strength);
-       COPY_SCALAR_FIELD(noWait);
+       COPY_SCALAR_FIELD(waitPolicy);
 
        return newnode;
 }
index 905468ee618a93834647a57e2ec0dd107e0eec05..358395f61f4a2ed903ccf31680fae514d8993afa 100644 (file)
@@ -2312,7 +2312,7 @@ _equalLockingClause(const LockingClause *a, const LockingClause *b)
 {
        COMPARE_NODE_FIELD(lockedRels);
        COMPARE_SCALAR_FIELD(strength);
-       COMPARE_SCALAR_FIELD(noWait);
+       COMPARE_SCALAR_FIELD(waitPolicy);
 
        return true;
 }
@@ -2408,7 +2408,7 @@ _equalRowMarkClause(const RowMarkClause *a, const RowMarkClause *b)
 {
        COMPARE_SCALAR_FIELD(rti);
        COMPARE_SCALAR_FIELD(strength);
-       COMPARE_SCALAR_FIELD(noWait);
+       COMPARE_SCALAR_FIELD(waitPolicy);
        COMPARE_SCALAR_FIELD(pushedDown);
 
        return true;
index 1ff78ebddd3f4b231f326d52181ea00698c3284d..56e486c8bc862a544da1eefadcddab17f6cc27b8 100644 (file)
@@ -836,7 +836,7 @@ _outPlanRowMark(StringInfo str, const PlanRowMark *node)
        WRITE_UINT_FIELD(prti);
        WRITE_UINT_FIELD(rowmarkId);
        WRITE_ENUM_FIELD(markType, RowMarkType);
-       WRITE_BOOL_FIELD(noWait);
+       WRITE_BOOL_FIELD(waitPolicy);
        WRITE_BOOL_FIELD(isParent);
 }
 
@@ -2136,7 +2136,7 @@ _outLockingClause(StringInfo str, const LockingClause *node)
 
        WRITE_NODE_FIELD(lockedRels);
        WRITE_ENUM_FIELD(strength, LockClauseStrength);
-       WRITE_BOOL_FIELD(noWait);
+       WRITE_ENUM_FIELD(waitPolicy, LockWaitPolicy);
 }
 
 static void
@@ -2327,7 +2327,7 @@ _outRowMarkClause(StringInfo str, const RowMarkClause *node)
 
        WRITE_UINT_FIELD(rti);
        WRITE_ENUM_FIELD(strength, LockClauseStrength);
-       WRITE_BOOL_FIELD(noWait);
+       WRITE_ENUM_FIELD(waitPolicy, LockWaitPolicy);
        WRITE_BOOL_FIELD(pushedDown);
 }
 
index a324100ed7ff78aaddbae41bdc9509099cb3a576..a3efdd4eff2cd7007c0d8d2c16464713805b6a2f 100644 (file)
@@ -321,7 +321,7 @@ _readRowMarkClause(void)
 
        READ_UINT_FIELD(rti);
        READ_ENUM_FIELD(strength, LockClauseStrength);
-       READ_BOOL_FIELD(noWait);
+       READ_ENUM_FIELD(waitPolicy, LockWaitPolicy);
        READ_BOOL_FIELD(pushedDown);
 
        READ_DONE();
index a509edd3affa5f8c9a431a3ed7efc68fe2bfa190..fb74d6bf1f41df08ce52b25622320e2710dc3aed 100644 (file)
@@ -2232,7 +2232,7 @@ preprocess_rowmarks(PlannerInfo *root)
                                newrc->markType = ROW_MARK_KEYSHARE;
                                break;
                }
-               newrc->noWait = rc->noWait;
+               newrc->waitPolicy = rc->waitPolicy;
                newrc->isParent = false;
 
                prowmarks = lappend(prowmarks, newrc);
@@ -2260,7 +2260,7 @@ preprocess_rowmarks(PlannerInfo *root)
                        newrc->markType = ROW_MARK_REFERENCE;
                else
                        newrc->markType = ROW_MARK_COPY;
-               newrc->noWait = false;  /* doesn't matter */
+               newrc->waitPolicy = LockWaitBlock;      /* doesn't matter */
                newrc->isParent = false;
 
                prowmarks = lappend(prowmarks, newrc);
index 51f10a488a83ece0b1fb26be35af8ee1a8f0096b..b625b5cffc29654888cc282b326317c419336546 100644 (file)
@@ -233,19 +233,19 @@ expand_security_qual(PlannerInfo *root, List *tlist, int rt_index,
                                {
                                        case ROW_MARK_EXCLUSIVE:
                                                applyLockingClause(subquery, 1, LCS_FORUPDATE,
-                                                                                  rc->noWait, false);
+                                                                                  rc->waitPolicy, false);
                                                break;
                                        case ROW_MARK_NOKEYEXCLUSIVE:
                                                applyLockingClause(subquery, 1, LCS_FORNOKEYUPDATE,
-                                                                                  rc->noWait, false);
+                                                                                  rc->waitPolicy, false);
                                                break;
                                        case ROW_MARK_SHARE:
                                                applyLockingClause(subquery, 1, LCS_FORSHARE,
-                                                                                  rc->noWait, false);
+                                                                                  rc->waitPolicy, false);
                                                break;
                                        case ROW_MARK_KEYSHARE:
                                                applyLockingClause(subquery, 1, LCS_FORKEYSHARE,
-                                                                                  rc->noWait, false);
+                                                                                  rc->waitPolicy, false);
                                                break;
                                        case ROW_MARK_REFERENCE:
                                        case ROW_MARK_COPY:
index 1cec511e0f00afb2e525d59207d57f974046445f..2fefc7d4f60d565931373809d1d03102b773cce3 100644 (file)
@@ -1389,7 +1389,7 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
                        newrc->prti = rti;
                        newrc->rowmarkId = oldrc->rowmarkId;
                        newrc->markType = oldrc->markType;
-                       newrc->noWait = oldrc->noWait;
+                       newrc->waitPolicy = oldrc->waitPolicy;
                        newrc->isParent = false;
 
                        root->rowMarks = lappend(root->rowMarks, newrc);
index fb6c44c11c822b07a2da5266ab5f0cd24a594f53..bd78e94985301e8b4fcbf28752e4ec1e951f9bcd 100644 (file)
@@ -2358,7 +2358,7 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
        allrels = makeNode(LockingClause);
        allrels->lockedRels = NIL;      /* indicates all rels */
        allrels->strength = lc->strength;
-       allrels->noWait = lc->noWait;
+       allrels->waitPolicy = lc->waitPolicy;
 
        if (lockedRels == NIL)
        {
@@ -2372,13 +2372,13 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
                        switch (rte->rtekind)
                        {
                                case RTE_RELATION:
-                                       applyLockingClause(qry, i,
-                                                                          lc->strength, lc->noWait, pushedDown);
+                                       applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
+                                                                          pushedDown);
                                        rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
                                        break;
                                case RTE_SUBQUERY:
-                                       applyLockingClause(qry, i,
-                                                                          lc->strength, lc->noWait, pushedDown);
+                                       applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
+                                                                          pushedDown);
 
                                        /*
                                         * FOR UPDATE/SHARE of subquery is propagated to all of
@@ -2424,15 +2424,13 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
                                        switch (rte->rtekind)
                                        {
                                                case RTE_RELATION:
-                                                       applyLockingClause(qry, i,
-                                                                                          lc->strength, lc->noWait,
-                                                                                          pushedDown);
+                                                       applyLockingClause(qry, i, lc->strength,
+                                                                                          lc->waitPolicy, pushedDown);
                                                        rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
                                                        break;
                                                case RTE_SUBQUERY:
-                                                       applyLockingClause(qry, i,
-                                                                                          lc->strength, lc->noWait,
-                                                                                          pushedDown);
+                                                       applyLockingClause(qry, i, lc->strength,
+                                                                                          lc->waitPolicy, pushedDown);
                                                        /* see comment above */
                                                        transformLockingClause(pstate, rte->subquery,
                                                                                                   allrels, true);
@@ -2499,7 +2497,8 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
  */
 void
 applyLockingClause(Query *qry, Index rtindex,
-                                  LockClauseStrength strength, bool noWait, bool pushedDown)
+                                  LockClauseStrength strength, LockWaitPolicy waitPolicy,
+                                  bool pushedDown)
 {
        RowMarkClause *rc;
 
@@ -2516,15 +2515,20 @@ applyLockingClause(Query *qry, Index rtindex,
                 * a shared and exclusive lock at the same time; it'll end up being
                 * exclusive anyway.)
                 *
-                * We also consider that NOWAIT wins if it's specified both ways. This
-                * 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.)
+                * Similarly, if the same RTE is specified with more than one lock wait
+                * policy, consider that NOWAIT wins over SKIP LOCKED, which in turn
+                * wins over waiting for the lock (the default).  This 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.)  Having NOWAIT win over SKIP
+                * LOCKED is reasonable since the former throws an error in case of
+                * coming across a locked tuple, which may be undesirable in some cases
+                * but it seems better than silently returning inconsistent results.
                 *
                 * And of course pushedDown becomes false if any clause is explicit.
                 */
                rc->strength = Max(rc->strength, strength);
-               rc->noWait |= noWait;
+               rc->waitPolicy = Max(rc->waitPolicy, waitPolicy);
                rc->pushedDown &= pushedDown;
                return;
        }
@@ -2533,7 +2537,7 @@ applyLockingClause(Query *qry, Index rtindex,
        rc = makeNode(RowMarkClause);
        rc->rti = rtindex;
        rc->strength = strength;
-       rc->noWait = noWait;
+       rc->waitPolicy = waitPolicy;
        rc->pushedDown = pushedDown;
        qry->rowMarks = lappend(qry->rowMarks, rc);
 }
index 77d2f29fc7ad2d428a0fd2f1202c530bb95e9366..c98c27a16d95f8811909850ae0ad7b1a7c18c22f 100644 (file)
@@ -284,6 +284,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <boolean>        opt_force opt_or_replace
                                opt_grant_grant_option opt_grant_admin_option
                                opt_nowait opt_if_exists opt_with_data
+%type <ival>   opt_nowait_or_skip
 
 %type <list>   OptRoleList AlterOptRoleList
 %type <defelt> CreateOptRoleElem AlterOptRoleElem
@@ -582,7 +583,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
        LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
        LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
-       LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOGGED
+       LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
 
        MAPPING MATCH MATERIALIZED MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
 
@@ -606,7 +607,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
        SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
        SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
-       SHOW SIMILAR SIMPLE SMALLINT SNAPSHOT SOME STABLE STANDALONE_P START
+       SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME STABLE STANDALONE_P START
        STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING
        SYMMETRIC SYSID SYSTEM_P
 
@@ -9370,6 +9371,12 @@ opt_nowait:      NOWAIT                                                  { $$ = TRUE; }
                        | /*EMPTY*/                                             { $$ = FALSE; }
                ;
 
+opt_nowait_or_skip:
+                       NOWAIT                                                  { $$ = LockWaitError; }
+                       | SKIP LOCKED                                   { $$ = LockWaitSkip; }
+                       | /*EMPTY*/                                             { $$ = LockWaitBlock; }
+               ;
+
 
 /*****************************************************************************
  *
@@ -10011,12 +10018,12 @@ for_locking_items:
                ;
 
 for_locking_item:
-                       for_locking_strength locked_rels_list opt_nowait
+                       for_locking_strength locked_rels_list opt_nowait_or_skip
                                {
                                        LockingClause *n = makeNode(LockingClause);
                                        n->lockedRels = $2;
                                        n->strength = $1;
-                                       n->noWait = $3;
+                                       n->waitPolicy = $3;
                                        $$ = (Node *) n;
                                }
                ;
@@ -13145,6 +13152,7 @@ unreserved_keyword:
                        | LOCAL
                        | LOCATION
                        | LOCK_P
+                       | LOCKED
                        | LOGGED
                        | MAPPING
                        | MATCH
@@ -13229,6 +13237,7 @@ unreserved_keyword:
                        | SHARE
                        | SHOW
                        | SIMPLE
+                       | SKIP
                        | SNAPSHOT
                        | STABLE
                        | STANDALONE_P
index 10d12cba17918cc1c4b43a9ee070ce20f71d8049..e7021509017dfd13ca6fb02d72da1a88dcd49157 100644 (file)
@@ -63,7 +63,8 @@ static void rewriteValuesRTE(RangeTblEntry *rte, Relation target_relation,
 static void rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
                                        Relation target_relation);
 static void markQueryForLocking(Query *qry, Node *jtnode,
-                                 LockClauseStrength strength, bool noWait, bool pushedDown);
+                                       LockClauseStrength strength, LockWaitPolicy waitPolicy,
+                                       bool pushedDown);
 static List *matchLocks(CmdType event, RuleLock *rulelocks,
                   int varno, Query *parsetree);
 static Query *fireRIRrules(Query *parsetree, List *activeRIRs,
@@ -1482,7 +1483,7 @@ ApplyRetrieveRule(Query *parsetree,
         */
        if (rc != NULL)
                markQueryForLocking(rule_action, (Node *) rule_action->jointree,
-                                                       rc->strength, rc->noWait, true);
+                                                       rc->strength, rc->waitPolicy, true);
 
        return parsetree;
 }
@@ -1500,7 +1501,8 @@ ApplyRetrieveRule(Query *parsetree,
  */
 static void
 markQueryForLocking(Query *qry, Node *jtnode,
-                                       LockClauseStrength strength, bool noWait, bool pushedDown)
+                                       LockClauseStrength strength, LockWaitPolicy waitPolicy,
+                                       bool pushedDown)
 {
        if (jtnode == NULL)
                return;
@@ -1511,15 +1513,15 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
                if (rte->rtekind == RTE_RELATION)
                {
-                       applyLockingClause(qry, rti, strength, noWait, pushedDown);
+                       applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
                        rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
                }
                else if (rte->rtekind == RTE_SUBQUERY)
                {
-                       applyLockingClause(qry, rti, strength, noWait, pushedDown);
+                       applyLockingClause(qry, rti, strength, waitPolicy, pushedDown);
                        /* FOR UPDATE/SHARE of subquery is propagated to subquery's rels */
                        markQueryForLocking(rte->subquery, (Node *) rte->subquery->jointree,
-                                                               strength, noWait, true);
+                                                               strength, waitPolicy, true);
                }
                /* other RTE types are unaffected by FOR UPDATE */
        }
@@ -1529,14 +1531,14 @@ markQueryForLocking(Query *qry, Node *jtnode,
                ListCell   *l;
 
                foreach(l, f->fromlist)
-                       markQueryForLocking(qry, lfirst(l), strength, noWait, pushedDown);
+                       markQueryForLocking(qry, lfirst(l), strength, waitPolicy, pushedDown);
        }
        else if (IsA(jtnode, JoinExpr))
        {
                JoinExpr   *j = (JoinExpr *) jtnode;
 
-               markQueryForLocking(qry, j->larg, strength, noWait, pushedDown);
-               markQueryForLocking(qry, j->rarg, strength, noWait, pushedDown);
+               markQueryForLocking(qry, j->larg, strength, waitPolicy, pushedDown);
+               markQueryForLocking(qry, j->rarg, strength, waitPolicy, pushedDown);
        }
        else
                elog(ERROR, "unrecognized node type: %d",
index 7237e5de839d5755634ab61270e284a2843f4f9b..6e41cbd142f024cdd48b99bba7a94fa740f07bcd 100644 (file)
@@ -4446,8 +4446,10 @@ get_select_query_def(Query *query, deparse_context *context,
                        appendStringInfo(buf, " OF %s",
                                                         quote_identifier(get_rtable_name(rc->rti,
                                                                                                                          context)));
-                       if (rc->noWait)
+                       if (rc->waitPolicy == LockWaitError)
                                appendStringInfoString(buf, " NOWAIT");
+                       else if (rc->waitPolicy == LockWaitSkip)
+                               appendStringInfoString(buf, " SKIP LOCKED");
                }
        }
 
index 493839f60e97dc12bdeca886db89b25f6bfb6f6a..7f7166d832e9db320c4243d86d939773fd424ac9 100644 (file)
@@ -19,6 +19,7 @@
 #include "nodes/primnodes.h"
 #include "storage/bufpage.h"
 #include "storage/lock.h"
+#include "utils/lockwaitpolicy.h"
 #include "utils/relcache.h"
 #include "utils/snapshot.h"
 
@@ -144,7 +145,7 @@ extern HTSU_Result heap_update(Relation relation, ItemPointer otid,
                        CommandId cid, Snapshot crosscheck, bool wait,
                        HeapUpdateFailureData *hufd, LockTupleMode *lockmode);
 extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
-                               CommandId cid, LockTupleMode mode, bool nowait,
+                               CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
                                bool follow_update,
                                Buffer *buffer, HeapUpdateFailureData *hufd);
 extern void heap_inplace_update(Relation relation, HeapTuple tuple);
index a3464a5df90cd4ebbb9ecc02b759cc868172133e..69ef30a3596f1208a7a090947895dddeafa4e643 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     201409293
+#define CATALOG_VERSION_NO     201410071
 
 #endif
index 02661350d94b85a9069b74377f6e9a7c40de16b7..d167b496fceb18e8c47664d92e637979db00fd24 100644 (file)
@@ -16,6 +16,7 @@
 
 #include "executor/execdesc.h"
 #include "nodes/parsenodes.h"
+#include "utils/lockwaitpolicy.h"
 
 
 /*
@@ -199,7 +200,8 @@ extern TupleTableSlot *EvalPlanQual(EState *estate, EPQState *epqstate,
                         Relation relation, Index rti, int lockmode,
                         ItemPointer tid, TransactionId priorXmax);
 extern HeapTuple EvalPlanQualFetch(EState *estate, Relation relation,
-                                 int lockmode, bool noWait, ItemPointer tid, TransactionId priorXmax);
+                                 int lockmode, LockWaitPolicy wait_policy, ItemPointer tid,
+                                 TransactionId priorXmax);
 extern void EvalPlanQualInit(EPQState *epqstate, EState *estate,
                                 Plan *subplan, List *auxrowmarks, int epqParam);
 extern void EvalPlanQualSetPlan(EPQState *epqstate,
index b271f213f597ec52d0488c0573c2ca64ef3670fb..39d2c10bdfecb2e484bca6858cc6026947419fec 100644 (file)
@@ -429,7 +429,7 @@ typedef struct ExecRowMark
        Index           prti;                   /* parent range table index, if child */
        Index           rowmarkId;              /* unique identifier for resjunk columns */
        RowMarkType markType;           /* see enum in nodes/plannodes.h */
-       bool            noWait;                 /* NOWAIT option */
+       LockWaitPolicy waitPolicy;      /* NOWAIT and SKIP LOCKED */
        ItemPointerData curCtid;        /* ctid of currently locked tuple, if any */
 } ExecRowMark;
 
index f3aa69e4a1b9698f6ef3a4fc4f0e6e24a5b0e42d..cef95446984c4ac0e574dd520803120ea3da8c4c 100644 (file)
@@ -23,6 +23,7 @@
 #include "nodes/bitmapset.h"
 #include "nodes/primnodes.h"
 #include "nodes/value.h"
+#include "utils/lockwaitpolicy.h"
 
 /* Possible sources of a Query */
 typedef enum QuerySource
@@ -631,7 +632,7 @@ typedef struct LockingClause
        NodeTag         type;
        List       *lockedRels;         /* FOR [KEY] UPDATE/SHARE relations */
        LockClauseStrength strength;
-       bool            noWait;                 /* NOWAIT option */
+       LockWaitPolicy  waitPolicy;     /* NOWAIT and SKIP LOCKED */
 } LockingClause;
 
 /*
@@ -976,7 +977,7 @@ typedef struct RowMarkClause
        NodeTag         type;
        Index           rti;                    /* range table index of target relation */
        LockClauseStrength strength;
-       bool            noWait;                 /* NOWAIT option */
+       LockWaitPolicy waitPolicy;      /* NOWAIT and SKIP LOCKED */
        bool            pushedDown;             /* pushed down from higher query level? */
 } RowMarkClause;
 
index 18394946f860dfb277f899e34083e44ad7ea589c..fb02390da51a7b2818603c23b4a399feafa2575b 100644 (file)
@@ -17,6 +17,7 @@
 #include "access/sdir.h"
 #include "nodes/bitmapset.h"
 #include "nodes/primnodes.h"
+#include "utils/lockwaitpolicy.h"
 
 
 /* ----------------------------------------------------------------
@@ -834,7 +835,7 @@ typedef struct PlanRowMark
        Index           prti;                   /* range table index of parent relation */
        Index           rowmarkId;              /* unique identifier for resjunk columns */
        RowMarkType markType;           /* see enum above */
-       bool            noWait;                 /* NOWAIT option */
+       LockWaitPolicy waitPolicy;  /* NOWAIT and SKIP LOCKED options */
        bool            isParent;               /* true if this is a "dummy" parent entry */
 } PlanRowMark;
 
index 370a445b7fca4dd5a25d667c7c0240582201655d..f5da6bfecef51eb82c56ee9d50eda9c51f3bbdbc 100644 (file)
@@ -39,6 +39,7 @@ extern bool analyze_requires_snapshot(Node *parseTree);
 extern char *LCS_asString(LockClauseStrength strength);
 extern void CheckSelectLocking(Query *qry, LockClauseStrength strength);
 extern void applyLockingClause(Query *qry, Index rtindex,
-                                  LockClauseStrength strength, bool noWait, bool pushedDown);
+                                  LockClauseStrength strength,
+                                  LockWaitPolicy waitPolicy, bool pushedDown);
 
 #endif   /* ANALYZE_H */
index 3c8c1b9e252a5d97716641066efafe87d2b34ece..e14dc9a1395db1af2a60832c62e7db9118bf1971 100644 (file)
@@ -230,6 +230,7 @@ PG_KEYWORD("localtime", LOCALTIME, RESERVED_KEYWORD)
 PG_KEYWORD("localtimestamp", LOCALTIMESTAMP, RESERVED_KEYWORD)
 PG_KEYWORD("location", LOCATION, UNRESERVED_KEYWORD)
 PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("locked", LOCKED, UNRESERVED_KEYWORD)
 PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD)
 PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD)
 PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD)
@@ -345,6 +346,7 @@ PG_KEYWORD("share", SHARE, UNRESERVED_KEYWORD)
 PG_KEYWORD("show", SHOW, UNRESERVED_KEYWORD)
 PG_KEYWORD("similar", SIMILAR, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("simple", SIMPLE, UNRESERVED_KEYWORD)
+PG_KEYWORD("skip", SKIP, UNRESERVED_KEYWORD)
 PG_KEYWORD("smallint", SMALLINT, COL_NAME_KEYWORD)
 PG_KEYWORD("snapshot", SNAPSHOT, UNRESERVED_KEYWORD)
 PG_KEYWORD("some", SOME, RESERVED_KEYWORD)
diff --git a/src/include/utils/lockwaitpolicy.h b/src/include/utils/lockwaitpolicy.h
new file mode 100644 (file)
index 0000000..7ec9c26
--- /dev/null
@@ -0,0 +1,31 @@
+/*-------------------------------------------------------------------------
+ * lockwaitpolicy.h
+ *       Header file for LockWaitPolicy enum.
+ *
+ * Copyright (c) 2014, PostgreSQL Global Development Group
+ *
+ * src/include/utils/lockwaitpolicy.h
+ *-------------------------------------------------------------------------
+ */
+#ifndef LOCKWAITPOLICY_H
+#define LOCKWAITPOLICY_H
+
+/*
+ * This enum controls how to deal with rows being locked by FOR UPDATE/SHARE
+ * clauses (i.e., NOWAIT and SKIP LOCKED clauses).  The ordering here is
+ * important, because the highest numerical value takes precedence when a
+ * RTE is specified multiple ways.  See applyLockingClause.
+ */
+typedef enum
+{
+       /* Wait for the lock to become available (default behavior) */
+       LockWaitBlock,
+
+       /* Skip rows that can't be locked (SKIP LOCKED) */
+       LockWaitSkip,
+
+       /* Raise an error if a row cannot be locked (NOWAIT) */
+       LockWaitError
+} LockWaitPolicy;
+
+#endif   /* LOCKWAITPOLICY_H */
index d8e8b351ed20e2824357fc2b03dd6733267dd70f..53e474fbb29884748fc1dba578e2e2b010ec627f 100644 (file)
@@ -104,7 +104,8 @@ typedef enum
        HeapTupleInvisible,
        HeapTupleSelfUpdated,
        HeapTupleUpdated,
-       HeapTupleBeingUpdated
+       HeapTupleBeingUpdated,
+       HeapTupleWouldBlock     /* can be returned by heap_tuple_lock */
 } HTSU_Result;
 
 #endif   /* SNAPSHOT_H */
diff --git a/src/test/isolation/expected/skip-locked-2.out b/src/test/isolation/expected/skip-locked-2.out
new file mode 100644 (file)
index 0000000..9240543
--- /dev/null
@@ -0,0 +1,49 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1a s2a s2b s1b s2c
+step s1a: SELECT * FROM queue ORDER BY id FOR SHARE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2a: SELECT * FROM queue ORDER BY id FOR SHARE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s1b: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s2a s1a s2b s1b s2c
+step s2a: SELECT * FROM queue ORDER BY id FOR SHARE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s1a: SELECT * FROM queue ORDER BY id FOR SHARE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s1b: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s2a s2b s1a s1b s2c
+step s2a: SELECT * FROM queue ORDER BY id FOR SHARE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s1a: SELECT * FROM queue ORDER BY id FOR SHARE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s1b: COMMIT;
+step s2c: COMMIT;
diff --git a/src/test/isolation/expected/skip-locked-3.out b/src/test/isolation/expected/skip-locked-3.out
new file mode 100644 (file)
index 0000000..fa8fe87
--- /dev/null
@@ -0,0 +1,19 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1a s2a s3a s1b s2b s3b
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE LIMIT 1; <waiting ...>
+step s3a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s1b: COMMIT;
+step s2a: <... completed>
+id             data           status         
+
+1              foo            NEW            
+step s2b: COMMIT;
+step s3b: COMMIT;
diff --git a/src/test/isolation/expected/skip-locked-4.out b/src/test/isolation/expected/skip-locked-4.out
new file mode 100644 (file)
index 0000000..2c9cfe8
--- /dev/null
@@ -0,0 +1,21 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s2a s1a s2b s2c s2d s2e s1b s2f
+step s2a: SELECT pg_advisory_lock(0);
+pg_advisory_lock
+
+               
+step s1a: SELECT * FROM foo WHERE pg_advisory_lock(0) IS NOT NULL ORDER BY id LIMIT 1 FOR UPDATE SKIP LOCKED; <waiting ...>
+step s2b: UPDATE foo SET data = data WHERE id = 1;
+step s2c: BEGIN;
+step s2d: UPDATE foo SET data = data WHERE id = 1;
+step s2e: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+
+t              
+step s1a: <... completed>
+id             data           
+
+2              x              
+step s1b: COMMIT;
+step s2f: COMMIT;
diff --git a/src/test/isolation/expected/skip-locked-4_1.out b/src/test/isolation/expected/skip-locked-4_1.out
new file mode 100644 (file)
index 0000000..552429a
--- /dev/null
@@ -0,0 +1,19 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s2a s1a s2b s2c s2d s2e s1b s2f
+step s2a: SELECT pg_advisory_lock(0);
+pg_advisory_lock
+
+               
+step s1a: SELECT * FROM foo WHERE pg_advisory_lock(0) IS NOT NULL ORDER BY id LIMIT 1 FOR UPDATE SKIP LOCKED; <waiting ...>
+step s2b: UPDATE foo SET data = data WHERE id = 1;
+step s2c: BEGIN;
+step s2d: UPDATE foo SET data = data WHERE id = 1;
+step s2e: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+
+t              
+step s1a: <... completed>
+error in steps s2e s1a: ERROR:  could not serialize access due to concurrent update
+step s1b: COMMIT;
+step s2f: COMMIT;
diff --git a/src/test/isolation/expected/skip-locked.out b/src/test/isolation/expected/skip-locked.out
new file mode 100644 (file)
index 0000000..f9b9cf2
--- /dev/null
@@ -0,0 +1,401 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1a s1b s1c s2a s2b s2c
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s1c: COMMIT;
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2c: COMMIT;
+
+starting permutation: s1a s1b s2a s1c s2b s2c
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s1c: COMMIT;
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2c: COMMIT;
+
+starting permutation: s1a s1b s2a s2b s1c s2c
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s1c: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s1a s1b s2a s2b s2c s1c
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s2c: COMMIT;
+step s1c: COMMIT;
+
+starting permutation: s1a s2a s1b s1c s2b s2c
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s1c: COMMIT;
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2c: COMMIT;
+
+starting permutation: s1a s2a s1b s2b s1c s2c
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s1c: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s1a s2a s1b s2b s2c s1c
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s2c: COMMIT;
+step s1c: COMMIT;
+
+starting permutation: s1a s2a s2b s1b s1c s2c
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s1c: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s1a s2a s2b s1b s2c s1c
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2c: COMMIT;
+step s1c: COMMIT;
+
+starting permutation: s1a s2a s2b s2c s1b s1c
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s2c: COMMIT;
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s1c: COMMIT;
+
+starting permutation: s2a s1a s1b s1c s2b s2c
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s1c: COMMIT;
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2c: COMMIT;
+
+starting permutation: s2a s1a s1b s2b s1c s2c
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s1c: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s2a s1a s1b s2b s2c s1c
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2c: COMMIT;
+step s1c: COMMIT;
+
+starting permutation: s2a s1a s2b s1b s1c s2c
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s1c: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s2a s1a s2b s1b s2c s1c
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s2c: COMMIT;
+step s1c: COMMIT;
+
+starting permutation: s2a s1a s2b s2c s1b s1c
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2c: COMMIT;
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s1c: COMMIT;
+
+starting permutation: s2a s2b s1a s1b s1c s2c
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s1c: COMMIT;
+step s2c: COMMIT;
+
+starting permutation: s2a s2b s1a s1b s2c s1c
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s2c: COMMIT;
+step s1c: COMMIT;
+
+starting permutation: s2a s2b s1a s2c s1b s1c
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+2              bar            NEW            
+step s2c: COMMIT;
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s1c: COMMIT;
+
+starting permutation: s2a s2b s2c s1a s1b s1c
+step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s2c: COMMIT;
+step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
+id             data           status         
+
+1              foo            NEW            
+step s1c: COMMIT;
index 3241a91505cf05f6db280673c29b79a50f6513c5..79a79568143a7cb18a0818eeaaf741d2bccc8161 100644 (file)
@@ -27,6 +27,10 @@ test: nowait-2
 test: nowait-3
 test: nowait-4
 test: nowait-5
+test: skip-locked
+test: skip-locked-2
+test: skip-locked-3
+test: skip-locked-4
 test: drop-index-concurrently-1
 test: alter-table-1
 test: timeouts
index 5dbebcabb03cb6f2bf343c3d86d91358e7fd328d..48ac777d78a18d8a20b77801a477ed16cde83d79 100644 (file)
@@ -27,4 +27,9 @@ step "s2d"    { UPDATE foo SET data = data; }
 step "s2e"     { SELECT pg_advisory_unlock(0); }
 step "s2f"     { COMMIT; }
 
+# s1 takes a snapshot but then waits on an advisory lock, then s2
+# updates the row in one transaction, then again in another without
+# committing, before allowing s1 to proceed to try to lock a row;
+# because it has a snapshot that sees the older version, we reach the
+# waiting code in EvalPlanQualFetch which ereports when in NOWAIT mode.
 permutation "s2a" "s1a" "s2b" "s2c" "s2d" "s2e" "s1b" "s2f"
diff --git a/src/test/isolation/specs/skip-locked-2.spec b/src/test/isolation/specs/skip-locked-2.spec
new file mode 100644 (file)
index 0000000..a179d34
--- /dev/null
@@ -0,0 +1,41 @@
+# Test SKIP LOCKED with multixact locks.
+
+setup
+{
+  CREATE TABLE queue (
+       id      int             PRIMARY KEY,
+       data                    text    NOT NULL,
+       status                  text    NOT NULL
+  );
+  INSERT INTO queue VALUES (1, 'foo', 'NEW'), (2, 'bar', 'NEW');
+}
+
+teardown
+{
+  DROP TABLE queue;
+}
+
+session "s1"
+setup          { BEGIN; }
+step "s1a"     { SELECT * FROM queue ORDER BY id FOR SHARE SKIP LOCKED LIMIT 1; }
+step "s1b"     { COMMIT; }
+
+session "s2"
+setup          { BEGIN; }
+step "s2a"     { SELECT * FROM queue ORDER BY id FOR SHARE SKIP LOCKED LIMIT 1; }
+step "s2b"     { SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; }
+step "s2c"     { COMMIT; }
+
+# s1 and s2 both get SHARE lock, creating a multixact lock, then s2
+# tries to update to UPDATE but skips the record because it can't
+# acquire a multixact lock
+permutation "s1a" "s2a" "s2b" "s1b" "s2c"
+
+# the same but with the SHARE locks acquired in a different order, so
+# s2 again skips because it can't acquired a multixact lock
+permutation "s2a" "s1a" "s2b" "s1b" "s2c"
+
+# s2 acquires SHARE then UPDATE, then s1 tries to acquire SHARE but
+# can't so skips the first record because it can't acquire a regular
+# lock
+permutation "s2a" "s2b" "s1a" "s1b" "s2c"
diff --git a/src/test/isolation/specs/skip-locked-3.spec b/src/test/isolation/specs/skip-locked-3.spec
new file mode 100644 (file)
index 0000000..30bf4c6
--- /dev/null
@@ -0,0 +1,36 @@
+# Test SKIP LOCKED with tuple locks.
+
+setup
+{
+  CREATE TABLE queue (
+       id      int             PRIMARY KEY,
+       data                    text    NOT NULL,
+       status                  text    NOT NULL
+  );
+  INSERT INTO queue VALUES (1, 'foo', 'NEW'), (2, 'bar', 'NEW');
+}
+
+teardown
+{
+  DROP TABLE queue;
+}
+
+session "s1"
+setup          { BEGIN; }
+step "s1a"     { SELECT * FROM queue ORDER BY id FOR UPDATE LIMIT 1; }
+step "s1b"     { COMMIT; }
+
+session "s2"
+setup          { BEGIN; }
+step "s2a"     { SELECT * FROM queue ORDER BY id FOR UPDATE LIMIT 1; }
+step "s2b"     { COMMIT; }
+
+session "s3"
+setup          { BEGIN; }
+step "s3a"     { SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; }
+step "s3b"     { COMMIT; }
+
+# s3 skips to the second record because it can't obtain the tuple lock
+# (s2 holds the tuple lock because it is next in line to obtain the
+# row lock, and s1 holds the row lock)
+permutation "s1a" "s2a" "s3a" "s1b" "s2b" "s3b"
diff --git a/src/test/isolation/specs/skip-locked-4.spec b/src/test/isolation/specs/skip-locked-4.spec
new file mode 100644 (file)
index 0000000..458e283
--- /dev/null
@@ -0,0 +1,36 @@
+# Test SKIP LOCKED with an updated tuple chain.
+
+setup
+{
+  CREATE TABLE foo (
+       id int PRIMARY KEY,
+       data text NOT NULL
+  );
+  INSERT INTO foo VALUES (1, 'x'), (2, 'x');
+}
+
+teardown
+{
+  DROP TABLE foo;
+}
+
+session "s1"
+setup          { BEGIN; }
+step "s1a"     { SELECT * FROM foo WHERE pg_advisory_lock(0) IS NOT NULL ORDER BY id LIMIT 1 FOR UPDATE SKIP LOCKED; }
+step "s1b"     { COMMIT; }
+
+session "s2"
+step "s2a"     { SELECT pg_advisory_lock(0); }
+step "s2b"     { UPDATE foo SET data = data WHERE id = 1; }
+step "s2c"     { BEGIN; }
+step "s2d"     { UPDATE foo SET data = data WHERE id = 1; }
+step "s2e"     { SELECT pg_advisory_unlock(0); }
+step "s2f"     { COMMIT; }
+
+# s1 takes a snapshot but then waits on an advisory lock, then s2
+# updates the row in one transaction, then again in another without
+# committing, before allowing s1 to proceed to try to lock a row;
+# because it has a snapshot that sees the older version, we reach the
+# waiting code in EvalPlanQualFetch which skips rows when in SKIP
+# LOCKED mode, so s1 sees the second row
+permutation "s2a" "s1a" "s2b" "s2c" "s2d" "s2e" "s1b" "s2f"
\ No newline at end of file
diff --git a/src/test/isolation/specs/skip-locked.spec b/src/test/isolation/specs/skip-locked.spec
new file mode 100644 (file)
index 0000000..3565963
--- /dev/null
@@ -0,0 +1,28 @@
+# Test SKIP LOCKED when regular row locks can't be acquired.
+
+setup
+{
+  CREATE TABLE queue (
+       id      int             PRIMARY KEY,
+       data                    text    NOT NULL,
+       status                  text    NOT NULL
+  );
+  INSERT INTO queue VALUES (1, 'foo', 'NEW'), (2, 'bar', 'NEW');
+}
+
+teardown
+{
+  DROP TABLE queue;
+}
+
+session "s1"
+setup          { BEGIN; }
+step "s1a"     { SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; }
+step "s1b"     { SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; }
+step "s1c"     { COMMIT; }
+
+session "s2"
+setup          { BEGIN; }
+step "s2a"     { SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; }
+step "s2b"     { SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; }
+step "s2c"     { COMMIT; }