aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/access/heap/heapam.c88
-rw-r--r--src/backend/access/index/indexam.c25
-rw-r--r--src/backend/access/nbtree/nbtinsert.c12
-rw-r--r--src/backend/access/nbtree/nbtpage.c7
-rw-r--r--src/backend/access/nbtree/nbtree.c2
-rw-r--r--src/backend/access/nbtree/nbtsearch.c13
-rw-r--r--src/backend/access/transam/twophase.c3
-rw-r--r--src/backend/access/transam/twophase_rmgr.c5
-rw-r--r--src/backend/access/transam/varsup.c5
-rw-r--r--src/backend/access/transam/xact.c21
-rw-r--r--src/backend/commands/variable.c38
-rw-r--r--src/backend/executor/nodeBitmapHeapscan.c3
-rw-r--r--src/backend/executor/nodeSeqscan.c5
-rw-r--r--src/backend/parser/gram.y6
-rw-r--r--src/backend/storage/ipc/ipci.c7
-rw-r--r--src/backend/storage/ipc/shmem.c2
-rw-r--r--src/backend/storage/ipc/shmqueue.c8
-rw-r--r--src/backend/storage/lmgr/Makefile2
-rw-r--r--src/backend/storage/lmgr/README4
-rw-r--r--src/backend/storage/lmgr/README-SSI537
-rw-r--r--src/backend/storage/lmgr/lwlock.c4
-rw-r--r--src/backend/storage/lmgr/predicate.c4439
-rw-r--r--src/backend/tcop/utility.c4
-rw-r--r--src/backend/utils/adt/lockfuncs.c79
-rw-r--r--src/backend/utils/misc/guc.c37
-rw-r--r--src/backend/utils/misc/postgresql.conf.sample4
-rw-r--r--src/backend/utils/resowner/resowner.c4
-rw-r--r--src/backend/utils/time/snapmgr.c15
-rw-r--r--src/bin/initdb/initdb.c1
-rw-r--r--src/bin/pg_dump/pg_dump.c35
-rw-r--r--src/include/access/heapam.h4
-rw-r--r--src/include/access/relscan.h1
-rw-r--r--src/include/access/twophase_rmgr.h5
-rw-r--r--src/include/access/xact.h15
-rw-r--r--src/include/catalog/pg_am.h42
-rw-r--r--src/include/commands/variable.h2
-rw-r--r--src/include/storage/lwlock.h12
-rw-r--r--src/include/storage/predicate.h67
-rw-r--r--src/include/storage/predicate_internals.h476
-rw-r--r--src/include/storage/shmem.h7
-rw-r--r--src/test/isolation/.gitignore12
-rw-r--r--src/test/isolation/Makefile74
-rw-r--r--src/test/isolation/README65
-rw-r--r--src/test/isolation/expected/classroom-scheduling.out299
-rw-r--r--src/test/isolation/expected/multiple-row-versions.out24
-rw-r--r--src/test/isolation/expected/partial-index.out641
-rw-r--r--src/test/isolation/expected/project-manager.out299
-rw-r--r--src/test/isolation/expected/receipt-report.out3379
-rw-r--r--src/test/isolation/expected/referential-integrity.out629
-rw-r--r--src/test/isolation/expected/ri-trigger.out111
-rw-r--r--src/test/isolation/expected/simple-write-skew.out41
-rw-r--r--src/test/isolation/expected/temporal-range-integrity.out299
-rw-r--r--src/test/isolation/expected/total-cash.out281
-rw-r--r--src/test/isolation/expected/two-ids.out1007
-rw-r--r--src/test/isolation/isolation_main.c89
-rw-r--r--src/test/isolation/isolation_schedule11
-rw-r--r--src/test/isolation/isolationtester.c372
-rw-r--r--src/test/isolation/isolationtester.h59
-rw-r--r--src/test/isolation/specparse.y188
-rw-r--r--src/test/isolation/specs/classroom-scheduling.spec29
-rw-r--r--src/test/isolation/specs/multiple-row-versions.spec48
-rw-r--r--src/test/isolation/specs/partial-index.spec32
-rw-r--r--src/test/isolation/specs/project-manager.spec30
-rw-r--r--src/test/isolation/specs/receipt-report.spec47
-rw-r--r--src/test/isolation/specs/referential-integrity.spec32
-rw-r--r--src/test/isolation/specs/ri-trigger.spec53
-rw-r--r--src/test/isolation/specs/simple-write-skew.spec30
-rw-r--r--src/test/isolation/specs/temporal-range-integrity.spec38
-rw-r--r--src/test/isolation/specs/total-cash.spec28
-rw-r--r--src/test/isolation/specs/two-ids.spec40
-rw-r--r--src/test/isolation/specscanner.l103
-rw-r--r--src/test/regress/expected/prepared_xacts.out12
-rw-r--r--src/test/regress/expected/prepared_xacts_1.out12
-rw-r--r--src/test/regress/expected/transactions.out2
-rw-r--r--src/test/regress/sql/prepared_xacts.sql12
-rw-r--r--src/test/regress/sql/transactions.sql2
-rw-r--r--src/tools/pgindent/typedefs.list18
77 files changed, 14403 insertions, 91 deletions
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 25d9fdea3a0..7dcc6015de9 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -57,6 +57,7 @@
#include "storage/bufmgr.h"
#include "storage/freespace.h"
#include "storage/lmgr.h"
+#include "storage/predicate.h"
#include "storage/procarray.h"
#include "storage/smgr.h"
#include "storage/standby.h"
@@ -261,20 +262,20 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
{
if (ItemIdIsNormal(lpp))
{
+ HeapTupleData loctup;
bool valid;
+ loctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
+ loctup.t_len = ItemIdGetLength(lpp);
+ ItemPointerSet(&(loctup.t_self), page, lineoff);
+
if (all_visible)
valid = true;
else
- {
- HeapTupleData loctup;
+ valid = HeapTupleSatisfiesVisibility(&loctup, snapshot, buffer);
- loctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
- loctup.t_len = ItemIdGetLength(lpp);
- ItemPointerSet(&(loctup.t_self), page, lineoff);
+ CheckForSerializableConflictOut(valid, scan->rs_rd, &loctup, buffer);
- valid = HeapTupleSatisfiesVisibility(&loctup, snapshot, buffer);
- }
if (valid)
scan->rs_vistuples[ntup++] = lineoff;
}
@@ -468,12 +469,16 @@ heapgettup(HeapScanDesc scan,
snapshot,
scan->rs_cbuf);
+ CheckForSerializableConflictOut(valid, scan->rs_rd, tuple, scan->rs_cbuf);
+
if (valid && key != NULL)
HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
nkeys, key, valid);
if (valid)
{
+ if (!scan->rs_relpredicatelocked)
+ PredicateLockTuple(scan->rs_rd, tuple);
LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
return;
}
@@ -741,12 +746,16 @@ heapgettup_pagemode(HeapScanDesc scan,
nkeys, key, valid);
if (valid)
{
+ if (!scan->rs_relpredicatelocked)
+ PredicateLockTuple(scan->rs_rd, tuple);
scan->rs_cindex = lineindex;
return;
}
}
else
{
+ if (!scan->rs_relpredicatelocked)
+ PredicateLockTuple(scan->rs_rd, tuple);
scan->rs_cindex = lineindex;
return;
}
@@ -1213,6 +1222,7 @@ heap_beginscan_internal(Relation relation, Snapshot snapshot,
scan->rs_strategy = NULL; /* set in initscan */
scan->rs_allow_strat = allow_strat;
scan->rs_allow_sync = allow_sync;
+ scan->rs_relpredicatelocked = false;
/*
* we can use page-at-a-time mode if it's an MVCC-safe snapshot
@@ -1459,8 +1469,13 @@ heap_fetch(Relation relation,
*/
valid = HeapTupleSatisfiesVisibility(tuple, snapshot, buffer);
+ if (valid)
+ PredicateLockTuple(relation, tuple);
+
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+ CheckForSerializableConflictOut(valid, relation, tuple, buffer);
+
if (valid)
{
/*
@@ -1506,13 +1521,15 @@ heap_fetch(Relation relation,
* heap_fetch, we do not report any pgstats count; caller may do so if wanted.
*/
bool
-heap_hot_search_buffer(ItemPointer tid, Buffer buffer, Snapshot snapshot,
- bool *all_dead)
+heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
+ Snapshot snapshot, bool *all_dead)
{
Page dp = (Page) BufferGetPage(buffer);
TransactionId prev_xmax = InvalidTransactionId;
OffsetNumber offnum;
bool at_chain_start;
+ bool valid;
+ bool match_found;
if (all_dead)
*all_dead = true;
@@ -1522,6 +1539,7 @@ heap_hot_search_buffer(ItemPointer tid, Buffer buffer, Snapshot snapshot,
Assert(ItemPointerGetBlockNumber(tid) == BufferGetBlockNumber(buffer));
offnum = ItemPointerGetOffsetNumber(tid);
at_chain_start = true;
+ match_found = false;
/* Scan through possible multiple members of HOT-chain */
for (;;)
@@ -1552,6 +1570,8 @@ heap_hot_search_buffer(ItemPointer tid, Buffer buffer, Snapshot snapshot,
heapTuple.t_data = (HeapTupleHeader) PageGetItem(dp, lp);
heapTuple.t_len = ItemIdGetLength(lp);
+ heapTuple.t_tableOid = relation->rd_id;
+ heapTuple.t_self = *tid;
/*
* Shouldn't see a HEAP_ONLY tuple at chain start.
@@ -1569,12 +1589,18 @@ heap_hot_search_buffer(ItemPointer tid, Buffer buffer, Snapshot snapshot,
break;
/* If it's visible per the snapshot, we must return it */
- if (HeapTupleSatisfiesVisibility(&heapTuple, snapshot, buffer))
+ valid = HeapTupleSatisfiesVisibility(&heapTuple, snapshot, buffer);
+ CheckForSerializableConflictOut(valid, relation, &heapTuple, buffer);
+ if (valid)
{
ItemPointerSetOffsetNumber(tid, offnum);
+ PredicateLockTuple(relation, &heapTuple);
if (all_dead)
*all_dead = false;
- return true;
+ if (IsolationIsSerializable())
+ match_found = true;
+ else
+ return true;
}
/*
@@ -1603,7 +1629,7 @@ heap_hot_search_buffer(ItemPointer tid, Buffer buffer, Snapshot snapshot,
break; /* end of chain */
}
- return false;
+ return match_found;
}
/*
@@ -1622,7 +1648,7 @@ heap_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot,
buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
LockBuffer(buffer, BUFFER_LOCK_SHARE);
- result = heap_hot_search_buffer(tid, buffer, snapshot, all_dead);
+ result = heap_hot_search_buffer(tid, relation, buffer, snapshot, all_dead);
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
ReleaseBuffer(buffer);
return result;
@@ -1729,6 +1755,7 @@ heap_get_latest_tid(Relation relation,
* result candidate.
*/
valid = HeapTupleSatisfiesVisibility(&tp, snapshot, buffer);
+ CheckForSerializableConflictOut(valid, relation, &tp, buffer);
if (valid)
*tid = ctid;
@@ -1893,6 +1920,13 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
buffer = RelationGetBufferForTuple(relation, heaptup->t_len,
InvalidBuffer, options, bistate);
+ /*
+ * We're about to do the actual insert -- check for conflict at the
+ * relation or buffer level first, to avoid possibly having to roll
+ * back work we've just done.
+ */
+ CheckForSerializableConflictIn(relation, NULL, buffer);
+
/* NO EREPORT(ERROR) from here till changes are logged */
START_CRIT_SECTION();
@@ -2193,6 +2227,12 @@ l1:
return result;
}
+ /*
+ * We're about to do the actual delete -- check for conflict first,
+ * to avoid possibly having to roll back work we've just done.
+ */
+ CheckForSerializableConflictIn(relation, &tp, buffer);
+
/* replace cid with a combo cid if necessary */
HeapTupleHeaderAdjustCmax(tp.t_data, &cid, &iscombo);
@@ -2546,6 +2586,12 @@ l2:
return result;
}
+ /*
+ * We're about to do the actual update -- check for conflict first,
+ * to avoid possibly having to roll back work we've just done.
+ */
+ CheckForSerializableConflictIn(relation, &oldtup, buffer);
+
/* Fill in OID and transaction status data for newtup */
if (relation->rd_rel->relhasoids)
{
@@ -2691,6 +2737,16 @@ l2:
}
/*
+ * We're about to create the new tuple -- check for conflict first,
+ * to avoid possibly having to roll back work we've just done.
+ *
+ * NOTE: For a tuple insert, we only need to check for table locks, since
+ * predicate locking at the index level will cover ranges for anything
+ * except a table scan. Therefore, only provide the relation.
+ */
+ CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
+
+ /*
* At this point newbuf and buffer are both pinned and locked, and newbuf
* has enough space for the new tuple. If they are the same buffer, only
* one pin is held.
@@ -2799,6 +2855,12 @@ l2:
END_CRIT_SECTION();
+ /*
+ * Any existing SIREAD locks on the old tuple must be linked to the new
+ * tuple for conflict detection purposes.
+ */
+ PredicateLockTupleRowVersionLink(relation, &oldtup, newtup);
+
if (newbuf != buffer)
LockBuffer(newbuf, BUFFER_LOCK_UNLOCK);
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index 32af32a2067..6e0db795176 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -64,9 +64,11 @@
#include "access/relscan.h"
#include "access/transam.h"
+#include "access/xact.h"
#include "pgstat.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
+#include "storage/predicate.h"
#include "utils/relcache.h"
#include "utils/snapmgr.h"
#include "utils/tqual.h"
@@ -192,6 +194,11 @@ index_insert(Relation indexRelation,
RELATION_CHECKS;
GET_REL_PROCEDURE(aminsert);
+ if (!(indexRelation->rd_am->ampredlocks))
+ CheckForSerializableConflictIn(indexRelation,
+ (HeapTuple) NULL,
+ InvalidBuffer);
+
/*
* have the am's insert proc do all the work.
*/
@@ -266,6 +273,9 @@ index_beginscan_internal(Relation indexRelation,
RELATION_CHECKS;
GET_REL_PROCEDURE(ambeginscan);
+ if (!(indexRelation->rd_am->ampredlocks))
+ PredicateLockRelation(indexRelation);
+
/*
* We hold a reference count to the relcache entry throughout the scan.
*/
@@ -523,6 +533,7 @@ index_getnext(IndexScanDesc scan, ScanDirection direction)
{
ItemId lp;
ItemPointer ctid;
+ bool valid;
/* check for bogus TID */
if (offnum < FirstOffsetNumber ||
@@ -577,8 +588,13 @@ index_getnext(IndexScanDesc scan, ScanDirection direction)
break;
/* If it's visible per the snapshot, we must return it */
- if (HeapTupleSatisfiesVisibility(heapTuple, scan->xs_snapshot,
- scan->xs_cbuf))
+ valid = HeapTupleSatisfiesVisibility(heapTuple, scan->xs_snapshot,
+ scan->xs_cbuf);
+
+ CheckForSerializableConflictOut(valid, scan->heapRelation,
+ heapTuple, scan->xs_cbuf);
+
+ if (valid)
{
/*
* If the snapshot is MVCC, we know that it could accept at
@@ -586,7 +602,8 @@ index_getnext(IndexScanDesc scan, ScanDirection direction)
* any more members. Otherwise, check for continuation of the
* HOT-chain, and set state for next time.
*/
- if (IsMVCCSnapshot(scan->xs_snapshot))
+ if (IsMVCCSnapshot(scan->xs_snapshot)
+ && !IsolationIsSerializable())
scan->xs_next_hot = InvalidOffsetNumber;
else if (HeapTupleIsHotUpdated(heapTuple))
{
@@ -598,6 +615,8 @@ index_getnext(IndexScanDesc scan, ScanDirection direction)
else
scan->xs_next_hot = InvalidOffsetNumber;
+ PredicateLockTuple(scan->heapRelation, heapTuple);
+
LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK);
pgstat_count_heap_fetch(scan->indexRelation);
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index 91b72b8f91d..0dd745f19a4 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -21,6 +21,7 @@
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
+#include "storage/predicate.h"
#include "utils/inval.h"
#include "utils/tqual.h"
@@ -174,6 +175,14 @@ top:
if (checkUnique != UNIQUE_CHECK_EXISTING)
{
+ /*
+ * The only conflict predicate locking cares about for indexes is when
+ * an index tuple insert conflicts with an existing lock. Since the
+ * actual location of the insert is hard to predict because of the
+ * random search used to prevent O(N^2) performance when there are many
+ * duplicate entries, we can just use the "first valid" page.
+ */
+ CheckForSerializableConflictIn(rel, NULL, buf);
/* do the insertion */
_bt_findinsertloc(rel, &buf, &offset, natts, itup_scankey, itup, heapRel);
_bt_insertonpg(rel, buf, stack, itup, offset, false);
@@ -696,6 +705,9 @@ _bt_insertonpg(Relation rel,
/* split the buffer into left and right halves */
rbuf = _bt_split(rel, buf, firstright,
newitemoff, itemsz, itup, newitemonleft);
+ PredicateLockPageSplit(rel,
+ BufferGetBlockNumber(buf),
+ BufferGetBlockNumber(rbuf));
/*----------
* By here,
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index db86ec9a1ad..27964455f7c 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -29,6 +29,7 @@
#include "storage/freespace.h"
#include "storage/indexfsm.h"
#include "storage/lmgr.h"
+#include "storage/predicate.h"
#include "utils/inval.h"
#include "utils/snapmgr.h"
@@ -1184,6 +1185,12 @@ _bt_pagedel(Relation rel, Buffer buf, BTStack stack)
RelationGetRelationName(rel));
/*
+ * Any insert which would have gone on the target block will now go to the
+ * right sibling block.
+ */
+ PredicateLockPageCombine(rel, target, rightsib);
+
+ /*
* Next find and write-lock the current parent of the target page. This is
* essentially the same as the corresponding step of splitting.
*/
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index d66bdd47e49..558ace1562b 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -29,6 +29,7 @@
#include "storage/indexfsm.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
+#include "storage/predicate.h"
#include "storage/smgr.h"
#include "utils/memutils.h"
@@ -822,6 +823,7 @@ restart:
if (_bt_page_recyclable(page))
{
/* Okay to recycle this page */
+ Assert(!PageIsPredicateLocked(rel, blkno));
RecordFreeIndexPage(rel, blkno);
vstate->totFreePages++;
stats->pages_deleted++;
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 42d956c6eac..cf74f7776ce 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -21,6 +21,7 @@
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/bufmgr.h"
+#include "storage/predicate.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
@@ -63,7 +64,10 @@ _bt_search(Relation rel, int keysz, ScanKey scankey, bool nextkey,
/* If index is empty and access = BT_READ, no root page is created. */
if (!BufferIsValid(*bufP))
+ {
+ PredicateLockRelation(rel); /* Nothing finer to lock exists. */
return (BTStack) NULL;
+ }
/* Loop iterates once per level descended in the tree */
for (;;)
@@ -88,7 +92,11 @@ _bt_search(Relation rel, int keysz, ScanKey scankey, bool nextkey,
page = BufferGetPage(*bufP);
opaque = (BTPageOpaque) PageGetSpecialPointer(page);
if (P_ISLEAF(opaque))
+ {
+ if (access == BT_READ)
+ PredicateLockPage(rel, BufferGetBlockNumber(*bufP));
break;
+ }
/*
* Find the appropriate item on the internal page, and get the child
@@ -1142,6 +1150,7 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
opaque = (BTPageOpaque) PageGetSpecialPointer(page);
if (!P_IGNORE(opaque))
{
+ PredicateLockPage(rel, blkno);
/* see if there are any matches on this page */
/* note that this will clear moreRight if we can stop */
if (_bt_readpage(scan, dir, P_FIRSTDATAKEY(opaque)))
@@ -1189,6 +1198,7 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
opaque = (BTPageOpaque) PageGetSpecialPointer(page);
if (!P_IGNORE(opaque))
{
+ PredicateLockPage(rel, BufferGetBlockNumber(so->currPos.buf));
/* see if there are any matches on this page */
/* note that this will clear moreLeft if we can stop */
if (_bt_readpage(scan, dir, PageGetMaxOffsetNumber(page)))
@@ -1352,6 +1362,7 @@ _bt_get_endpoint(Relation rel, uint32 level, bool rightmost)
if (!BufferIsValid(buf))
{
/* empty index... */
+ PredicateLockRelation(rel); /* Nothing finer to lock exists. */
return InvalidBuffer;
}
@@ -1431,10 +1442,12 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
if (!BufferIsValid(buf))
{
/* empty index... */
+ PredicateLockRelation(rel); /* Nothing finer to lock exists. */
so->currPos.buf = InvalidBuffer;
return false;
}
+ PredicateLockPage(rel, BufferGetBlockNumber(buf));
page = BufferGetPage(buf);
opaque = (BTPageOpaque) PageGetSpecialPointer(page);
Assert(P_ISLEAF(opaque));
diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index 4fee9c3244b..287ad266980 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -57,6 +57,7 @@
#include "pgstat.h"
#include "replication/walsender.h"
#include "storage/fd.h"
+#include "storage/predicate.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
@@ -1357,6 +1358,8 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
else
ProcessRecords(bufptr, xid, twophase_postabort_callbacks);
+ PredicateLockTwoPhaseFinish(xid, isCommit);
+
/* Count the prepared xact as committed or aborted */
AtEOXact_PgStat(isCommit);
diff --git a/src/backend/access/transam/twophase_rmgr.c b/src/backend/access/transam/twophase_rmgr.c
index 02de1e85269..47c15af241d 100644
--- a/src/backend/access/transam/twophase_rmgr.c
+++ b/src/backend/access/transam/twophase_rmgr.c
@@ -18,12 +18,14 @@
#include "access/twophase_rmgr.h"
#include "pgstat.h"
#include "storage/lock.h"
+#include "storage/predicate.h"
const TwoPhaseCallback twophase_recover_callbacks[TWOPHASE_RM_MAX_ID + 1] =
{
NULL, /* END ID */
lock_twophase_recover, /* Lock */
+ predicatelock_twophase_recover, /* PredicateLock */
NULL, /* pgstat */
multixact_twophase_recover /* MultiXact */
};
@@ -32,6 +34,7 @@ const TwoPhaseCallback twophase_postcommit_callbacks[TWOPHASE_RM_MAX_ID + 1] =
{
NULL, /* END ID */
lock_twophase_postcommit, /* Lock */
+ NULL, /* PredicateLock */
pgstat_twophase_postcommit, /* pgstat */
multixact_twophase_postcommit /* MultiXact */
};
@@ -40,6 +43,7 @@ const TwoPhaseCallback twophase_postabort_callbacks[TWOPHASE_RM_MAX_ID + 1] =
{
NULL, /* END ID */
lock_twophase_postabort, /* Lock */
+ NULL, /* PredicateLock */
pgstat_twophase_postabort, /* pgstat */
multixact_twophase_postabort /* MultiXact */
};
@@ -48,6 +52,7 @@ const TwoPhaseCallback twophase_standby_recover_callbacks[TWOPHASE_RM_MAX_ID + 1
{
NULL, /* END ID */
lock_twophase_standby_recover, /* Lock */
+ NULL, /* PredicateLock */
NULL, /* pgstat */
NULL /* MultiXact */
};
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index a03ec85f8fc..a828b3de48f 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -21,6 +21,7 @@
#include "miscadmin.h"
#include "postmaster/autovacuum.h"
#include "storage/pmsignal.h"
+#include "storage/predicate.h"
#include "storage/proc.h"
#include "utils/builtins.h"
#include "utils/syscache.h"
@@ -161,6 +162,10 @@ GetNewTransactionId(bool isSubXact)
ExtendCLOG(xid);
ExtendSUBTRANS(xid);
+ /* If it's top level, the predicate locking system also needs to know. */
+ if (!isSubXact)
+ RegisterPredicateLockingXid(xid);
+
/*
* Now advance the nextXid counter. This must not happen until after we
* have successfully completed ExtendCLOG() --- if that routine fails, we
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 1e31e07ec97..a0170b42e20 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -40,6 +40,7 @@
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/lmgr.h"
+#include "storage/predicate.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
@@ -63,6 +64,9 @@ int XactIsoLevel;
bool DefaultXactReadOnly = false;
bool XactReadOnly;
+bool DefaultXactDeferrable = false;
+bool XactDeferrable;
+
bool XactSyncCommit = true;
int CommitDelay = 0; /* precommit delay in microseconds */
@@ -1640,6 +1644,7 @@ StartTransaction(void)
s->startedInRecovery = false;
XactReadOnly = DefaultXactReadOnly;
}
+ XactDeferrable = DefaultXactDeferrable;
XactIsoLevel = DefaultXactIsoLevel;
forceSyncCommit = false;
MyXactAccessedTempRel = false;
@@ -1787,6 +1792,13 @@ CommitTransaction(void)
AtEOXact_LargeObject(true);
/*
+ * Mark serializable transaction as complete for predicate locking
+ * purposes. This should be done as late as we can put it and still
+ * allow errors to be raised for failure patterns found at commit.
+ */
+ PreCommit_CheckForSerializationFailure();
+
+ /*
* Insert notifications sent by NOTIFY commands into the queue. This
* should be late in the pre-commit sequence to minimize time spent
* holding the notify-insertion lock.
@@ -1980,6 +1992,13 @@ PrepareTransaction(void)
/* close large objects before lower-level cleanup */
AtEOXact_LargeObject(true);
+ /*
+ * Mark serializable transaction as complete for predicate locking
+ * purposes. This should be done as late as we can put it and still
+ * allow errors to be raised for failure patterns found at commit.
+ */
+ PreCommit_CheckForSerializationFailure();
+
/* NOTIFY will be handled below */
/*
@@ -2044,6 +2063,7 @@ PrepareTransaction(void)
AtPrepare_Notify();
AtPrepare_Locks();
+ AtPrepare_PredicateLocks();
AtPrepare_PgStat();
AtPrepare_MultiXact();
AtPrepare_RelationMap();
@@ -2103,6 +2123,7 @@ PrepareTransaction(void)
PostPrepare_MultiXact(xid);
PostPrepare_Locks(xid);
+ PostPrepare_PredicateLocks(xid);
ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_LOCKS,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 139148eec2e..2a61ea3bc7c 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -616,6 +616,15 @@ assign_XactIsoLevel(const char *value, bool doit, GucSource source)
errmsg("SET TRANSACTION ISOLATION LEVEL must not be called in a subtransaction")));
return NULL;
}
+ /* Can't go to serializable mode while recovery is still active */
+ if (RecoveryInProgress() && strcmp(value, "serializable") == 0)
+ {
+ ereport(GUC_complaint_elevel(source),
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot use serializable mode in a hot standby"),
+ errhint("You can use REPEATABLE READ instead.")));
+ return false;
+ }
}
if (strcmp(value, "serializable") == 0)
@@ -667,6 +676,35 @@ show_XactIsoLevel(void)
}
}
+/*
+ * SET TRANSACTION [NOT] DEFERRABLE
+ */
+
+bool
+assign_transaction_deferrable(bool newval, bool doit, GucSource source)
+{
+ /* source == PGC_S_OVERRIDE means do it anyway, eg at xact abort */
+ if (source == PGC_S_OVERRIDE)
+ return true;
+
+ if (IsSubTransaction())
+ {
+ ereport(GUC_complaint_elevel(source),
+ (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
+ errmsg("SET TRANSACTION [NOT] DEFERRABLE cannot be called within a subtransaction")));
+ return false;
+ }
+
+ if (FirstSnapshotSet)
+ {
+ ereport(GUC_complaint_elevel(source),
+ (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
+ errmsg("SET TRANSACTION [NOT] DEFERRABLE must be called before any query")));
+ return false;
+ }
+
+ return true;
+}
/*
* Random number seed
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index 7178b4c17d0..20d5eb14bf2 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -42,6 +42,7 @@
#include "executor/nodeBitmapHeapscan.h"
#include "pgstat.h"
#include "storage/bufmgr.h"
+#include "storage/predicate.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
#include "utils/tqual.h"
@@ -351,7 +352,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
ItemPointerData tid;
ItemPointerSet(&tid, page, offnum);
- if (heap_hot_search_buffer(&tid, buffer, snapshot, NULL))
+ if (heap_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot, NULL))
scan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid);
}
}
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index 0f3438d0639..1e566b2d505 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -28,6 +28,7 @@
#include "access/relscan.h"
#include "executor/execdebug.h"
#include "executor/nodeSeqscan.h"
+#include "storage/predicate.h"
static void InitScanRelation(SeqScanState *node, EState *estate);
static TupleTableSlot *SeqNext(SeqScanState *node);
@@ -105,11 +106,15 @@ SeqRecheck(SeqScanState *node, TupleTableSlot *slot)
* tuple.
* We call the ExecScan() routine and pass it the appropriate
* access method functions.
+ * For serializable transactions, we first acquire a predicate
+ * lock on the entire relation.
* ----------------------------------------------------------------
*/
TupleTableSlot *
ExecSeqScan(SeqScanState *node)
{
+ PredicateLockRelation(node->ss_currentRelation);
+ node->ss_currentScanDesc->rs_relpredicatelocked = true;
return ExecScan((ScanState *) node,
(ExecScanAccessMtd) SeqNext,
(ExecScanRecheckMtd) SeqRecheck);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 456db5c50ef..21782824ca1 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -6768,6 +6768,12 @@ transaction_mode_item:
| READ WRITE
{ $$ = makeDefElem("transaction_read_only",
makeIntConst(FALSE, @1)); }
+ | DEFERRABLE
+ { $$ = makeDefElem("transaction_deferrable",
+ makeIntConst(TRUE, @1)); }
+ | NOT DEFERRABLE
+ { $$ = makeDefElem("transaction_deferrable",
+ makeIntConst(FALSE, @1)); }
;
/* Syntax with commas is SQL-spec, without commas is Postgres historical */
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 2dbac56c25d..56c0bd8d498 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -32,6 +32,7 @@
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
+#include "storage/predicate.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
@@ -105,6 +106,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
sizeof(ShmemIndexEnt)));
size = add_size(size, BufferShmemSize());
size = add_size(size, LockShmemSize());
+ size = add_size(size, PredicateLockShmemSize());
size = add_size(size, ProcGlobalShmemSize());
size = add_size(size, XLOGShmemSize());
size = add_size(size, CLOGShmemSize());
@@ -200,6 +202,11 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
InitLocks();
/*
+ * Set up predicate lock manager
+ */
+ InitPredicateLocks();
+
+ /*
* Set up process table
*/
if (!IsUnderPostmaster)
diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c
index 62809577991..0811da7b6fe 100644
--- a/src/backend/storage/ipc/shmem.c
+++ b/src/backend/storage/ipc/shmem.c
@@ -198,7 +198,7 @@ ShmemAlloc(Size size)
* Returns TRUE if the pointer points within the shared memory segment.
*/
bool
-ShmemAddrIsValid(void *addr)
+ShmemAddrIsValid(const void *addr)
{
return (addr >= ShmemBase) && (addr < ShmemEnd);
}
diff --git a/src/backend/storage/ipc/shmqueue.c b/src/backend/storage/ipc/shmqueue.c
index 0277c3f9f40..1cf69a09c83 100644
--- a/src/backend/storage/ipc/shmqueue.c
+++ b/src/backend/storage/ipc/shmqueue.c
@@ -43,14 +43,12 @@ SHMQueueInit(SHM_QUEUE *queue)
* SHMQueueIsDetached -- TRUE if element is not currently
* in a queue.
*/
-#ifdef NOT_USED
bool
-SHMQueueIsDetached(SHM_QUEUE *queue)
+SHMQueueIsDetached(const SHM_QUEUE *queue)
{
Assert(ShmemAddrIsValid(queue));
return (queue->prev == NULL);
}
-#endif
/*
* SHMQueueElemInit -- clear an element's links
@@ -146,7 +144,7 @@ SHMQueueInsertAfter(SHM_QUEUE *queue, SHM_QUEUE *elem)
*--------------------
*/
Pointer
-SHMQueueNext(SHM_QUEUE *queue, SHM_QUEUE *curElem, Size linkOffset)
+SHMQueueNext(const SHM_QUEUE *queue, const SHM_QUEUE *curElem, Size linkOffset)
{
SHM_QUEUE *elemPtr = curElem->next;
@@ -162,7 +160,7 @@ SHMQueueNext(SHM_QUEUE *queue, SHM_QUEUE *curElem, Size linkOffset)
* SHMQueueEmpty -- TRUE if queue head is only element, FALSE otherwise
*/
bool
-SHMQueueEmpty(SHM_QUEUE *queue)
+SHMQueueEmpty(const SHM_QUEUE *queue)
{
Assert(ShmemAddrIsValid(queue));
diff --git a/src/backend/storage/lmgr/Makefile b/src/backend/storage/lmgr/Makefile
index 9aa9a5c0868..e12a8549f74 100644
--- a/src/backend/storage/lmgr/Makefile
+++ b/src/backend/storage/lmgr/Makefile
@@ -12,7 +12,7 @@ subdir = src/backend/storage/lmgr
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
-OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o
+OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/storage/lmgr/README b/src/backend/storage/lmgr/README
index 87cae18cb6f..40779d2e359 100644
--- a/src/backend/storage/lmgr/README
+++ b/src/backend/storage/lmgr/README
@@ -3,7 +3,7 @@ src/backend/storage/lmgr/README
Locking Overview
================
-Postgres uses three types of interprocess locks:
+Postgres uses four types of interprocess locks:
* Spinlocks. These are intended for *very* short-term locks. If a lock
is to be held more than a few dozen instructions, or across any sort of
@@ -34,6 +34,8 @@ supports a variety of lock modes with table-driven semantics, and it has
full deadlock detection and automatic release at transaction end.
Regular locks should be used for all user-driven lock requests.
+* SIReadLock predicate locks. See separate README-SSI file for details.
+
Acquisition of either a spinlock or a lightweight lock causes query
cancel and die() interrupts to be held off until all such locks are
released. No such restriction exists for regular locks, however. Also
diff --git a/src/backend/storage/lmgr/README-SSI b/src/backend/storage/lmgr/README-SSI
new file mode 100644
index 00000000000..a2bb63e3f8b
--- /dev/null
+++ b/src/backend/storage/lmgr/README-SSI
@@ -0,0 +1,537 @@
+src/backend/storage/lmgr/README-SSI
+
+Serializable Snapshot Isolation (SSI) and Predicate Locking
+===========================================================
+
+This is currently sitting in the lmgr directory because about 90% of
+the code is an implementation of predicate locking, which is required
+for SSI, rather than being directly related to SSI itself. When
+another use for predicate locking justifies the effort to tease these
+two things apart, this README file should probably be split.
+
+
+Credits
+-------
+
+This feature was developed by Kevin Grittner and Dan R. K. Ports,
+with review and suggestions from Joe Conway, Heikki Linnakangas, and
+Jeff Davis. It is based on work published in these papers:
+
+ Michael J. Cahill, Uwe Röhm, and Alan D. Fekete. 2008.
+ Serializable isolation for snapshot databases.
+ In SIGMOD ’08: Proceedings of the 2008 ACM SIGMOD
+ international conference on Management of data,
+ pages 729–738, New York, NY, USA. ACM.
+ http://doi.acm.org/10.1145/1376616.1376690
+
+ Michael James Cahill. 2009.
+ Serializable Isolation for Snapshot Databases.
+ Sydney Digital Theses.
+ University of Sydney, School of Information Technologies.
+ http://hdl.handle.net/2123/5353
+
+
+Overview
+--------
+
+With true serializable transactions, if you can show that your
+transaction will do the right thing if there are no concurrent
+transactions, it will do the right thing in any mix of serializable
+transactions or be rolled back with a serialization failure. This
+feature has been implemented in PostgreSQL using SSI.
+
+
+Serializable and Snapshot Transaction Isolation Levels
+------------------------------------------------------
+
+Serializable transaction isolation is attractive for shops with
+active development by many programmers against a complex schema
+because it guarantees data integrity with very little staff time --
+if a transaction can be shown to always do the right thing when it is
+run alone (before or after any other transaction), it will always do
+the right thing in any mix of concurrent serializable transactions.
+Where conflicts with other transactions would result in an
+inconsistent state within the database, or an inconsistent view of
+the data, a serializable transaction will block or roll back to
+prevent the anomaly. The SQL standard provides a specific SQLSTATE
+for errors generated when a transaction rolls back for this reason,
+so that transactions can be retried automatically.
+
+Before version 9.1 PostgreSQL did not support a full serializable
+isolation level. A request for serializable transaction isolation
+actually provided snapshot isolation. This has well known anomalies
+which can allow data corruption or inconsistent views of the data
+during concurrent transactions; although these anomalies only occur
+when certain patterns of read-write dependencies exist within a set
+of concurrent transactions. Where these patterns exist, the anomalies
+can be prevented by introducing conflicts through explicitly
+programmed locks or otherwise unnecessary writes to the database.
+Snapshot isolation is popular because performance is better than
+serializable isolation and the integrity guarantees which it does
+provide allow anomalies to be avoided or managed with reasonable
+effort in many environments.
+
+
+Serializable Isolation Implementation Strategies
+------------------------------------------------
+
+Techniques for implementing full serializable isolation have been
+published and in use in many database products for decades. The
+primary technique which has been used is Strict 2 Phase Locking
+(S2PL), which operates by blocking writes against data which has been
+read by concurrent transactions and blocking any access (read or
+write) against data which has been written by concurrent
+transactions. A cycle in a graph of blocking indicates a deadlock,
+requiring a rollback. Blocking and deadlocks under S2PL in high
+contention workloads can be debilitating, crippling throughput and
+response time.
+
+A new technique for implementing full serializable isolation in an
+MVCC database appears in the literature beginning in 2008. This
+technique, known as Serializable Snapshot Isolation (SSI) has many of
+the advantages of snapshot isolation. In particular, reads don't
+block anything and writes don't block reads. Essentially, it runs
+snapshot isolation but monitors the read-write conflicts between
+transactions to identify dangerous structures in the transaction
+graph which indicate that a set of concurrent transactions might
+produce an anomaly, and rolls back transactions to ensure that no
+anomalies occur. It will produce some false positives (where a
+transaction is rolled back even though there would not have been an
+anomaly), but will never let an anomaly occur. In the two known
+prototype implementations, performance for many workloads (even with
+the need to restart transactions which are rolled back) is very close
+to snapshot isolation and generally far better than an S2PL
+implementation.
+
+
+Apparent Serial Order of Execution
+----------------------------------
+
+One way to understand when snapshot anomalies can occur, and to
+visualize the difference between the serializable implementations
+described above, is to consider that among transactions executing at
+the serializable transaction isolation level, the results are
+required to be consistent with some serial (one-at-a-time) execution
+of the transactions[1]. How is that order determined in each?
+
+S2PL locks rows used by the transaction in a way which blocks
+conflicting access, so that at the moment of a successful commit it
+is certain that no conflicting access has occurred. Some transactions
+may have blocked, essentially being partially serialized with the
+committing transaction, to allow this. Some transactions may have
+been rolled back, due to cycles in the blocking. But with S2PL,
+transactions can always be viewed as having occurred serially, in the
+order of successful commit.
+
+With snapshot isolation, reads never block writes, nor vice versa, so
+there is much less actual serialization. The order in which
+transactions appear to have executed is determined by something more
+subtle than in S2PL: read/write dependencies. If a transaction
+attempts to read data which is not visible to it because the
+transaction which wrote it (or will later write it) is concurrent
+(one of them was running when the other acquired its snapshot), then
+the reading transaction appears to have executed first, regardless of
+the actual sequence of transaction starts or commits (since it sees a
+database state prior to that in which the other transaction leaves
+it). If one transaction has both rw-dependencies in (meaning that a
+concurrent transaction attempts to read data it writes) and out
+(meaning it attempts to read data a concurrent transaction writes),
+and a couple other conditions are met, there can appear to be a cycle
+in execution order of the transactions. This is when the anomalies
+occur.
+
+SSI works by watching for the conditions mentioned above, and rolling
+back a transaction when needed to prevent any anomaly. The apparent
+order of execution will always be consistent with any actual
+serialization (i.e., a transaction which run by itself can always be
+considered to have run after any transactions committed before it
+started and before any transacton which starts after it commits); but
+among concurrent transactions it will appear that the transaction on
+the read side of a rw-dependency executed before the transaction on
+the write side.
+
+
+PostgreSQL Implementation
+-------------------------
+
+The implementation of serializable transactions for PostgreSQL is
+accomplished through Serializable Snapshot Isolation (SSI), based on
+the work of Cahill, et al. Fundamentally, this allows snapshot
+isolation to run as it has, while monitoring for conditions which
+could create a serialization anomaly.
+
+ * Since this technique is based on Snapshot Isolation (SI), those
+areas in PostgreSQL which don't use SI can't be brought under SSI.
+This includes system tables, temporary tables, sequences, hint bit
+rewrites, etc. SSI can not eliminate existing anomalies in these
+areas.
+
+ * Any transaction which is run at a transaction isolation level
+other than SERIALIZABLE will not be affected by SSI. If you want to
+enforce business rules through SSI, all transactions should be run at
+the SERIALIZABLE transaction isolation level, and that should
+probably be set as the default.
+
+ * If all transactions are run at the SERIALIZABLE transaction
+isolation level, business rules can be enforced in triggers or
+application code without ever having a need to acquire an explicit
+lock or to use SELECT FOR SHARE or SELECT FOR UPDATE.
+
+ * Those who want to continue to use snapshot isolation without
+the additional protections of SSI (and the associated costs of
+enforcing those protections), can use the REPEATABLE READ transaction
+isolation level. This level will retain its legacy behavior, which
+is identical to the old SERIALIZABLE implementation and fully
+consistent with the standard's requirements for the REPEATABLE READ
+transaction isolation level.
+
+ * Performance under this SSI implementation will be significantly
+improved if transactions which don't modify permanent tables are
+declared to be READ ONLY before they begin reading data.
+
+ * Performance under SSI will tend to degrade more rapidly with a
+large number of active database transactions than under less strict
+isolation levels. Limiting the number of active transactions through
+use of a connection pool or similar techniques may be necessary to
+maintain good performance.
+
+ * Any transaction which must be rolled back to prevent
+serialization anomalies will fail with SQLSTATE 40001, which has a
+standard meaning of "serialization failure".
+
+ * This SSI implementation makes an effort to choose the
+transaction to be cancelled such that an immediate retry of the
+transaction will not fail due to conflicts with exactly the same
+transactions. Pursuant to this goal, no transaction is cancelled
+until one of the other transactions in the set of conflicts which
+could generate an anomaly has successfully committed. This is
+conceptually similar to how write conflicts are handled. To fully
+implement this guarantee there needs to be a way to roll back the
+active transaction for another process with a serialization failure
+SQLSTATE, even if it is "idle in transaction".
+
+
+Predicate Locking
+-----------------
+
+Both S2PL and SSI require some form of predicate locking to handle
+situations where reads conflict with later inserts or with later
+updates which move data into the selected range. PostgreSQL didn't
+already have predicate locking, so it needed to be added to support
+full serializable transactions under either strategy. Practical
+implementations of predicate locking generally involve acquiring
+locks against data as it is accessed, using multiple granularities
+(tuple, page, table, etc.) with escalation as needed to keep the lock
+count to a number which can be tracked within RAM structures, and
+this was used in PostgreSQL. Coarse granularities can cause some
+false positive indications of conflict. The number of false positives
+can be influenced by plan choice.
+
+
+Implementation overview
+-----------------------
+
+New RAM structures, inspired by those used to track traditional locks
+in PostgreSQL, but tailored to the needs of SIREAD predicate locking,
+are used. These refer to physical objects actually accessed in the
+course of executing the query, to model the predicates through
+inference. Anyone interested in this subject should review the
+Hellerstein, Stonebraker and Hamilton paper[2], along with the
+locking papers referenced from that and the Cahill papers.
+
+Because the SIREAD locks don't block, traditional locking techniques
+were be modified. Intent locking (locking higher level objects
+before locking lower level objects) doesn't work with non-blocking
+"locks" (which are, in some respects, more like flags than locks).
+
+A configurable amount of shared memory is reserved at postmaster
+start-up to track predicate locks. This size cannot be changed
+without a restart.
+
+ * To prevent resource exhaustion, multiple fine-grained locks may
+be promoted to a single coarser-grained lock as needed.
+
+ * An attempt to acquire an SIREAD lock on a tuple when the same
+transaction already holds an SIREAD lock on the page or the relation
+will be ignored. Likewise, an attempt to lock a page when the
+relation is locked will be ignored, and the acquisition of a coarser
+lock will result in the automatic release of all finer-grained locks
+it covers.
+
+
+Heap locking
+------------
+
+Predicate locks will be acquired for the heap based on the following:
+
+ * For a table scan, the entire relation will be locked.
+
+ * Each tuple read which is visible to the reading transaction
+will be locked, whether or not it meets selection criteria; except
+that there is no need to acquire an SIREAD lock on a tuple when the
+transaction already holds a write lock on any tuple representing the
+row, since a rw-dependency would also create a ww-dependency which
+has more aggressive enforcement and will thus prevent any anomaly.
+
+
+Index AM implementations
+------------------------
+
+Since predicate locks only exist to detect writes which conflict with
+earlier reads, and heap tuple locks are acquired to cover all heap
+tuples actually read, including those read through indexes, the index
+tuples which were actually scanned are not of interest in themselves;
+we only care about their "new neighbors" -- later inserts into the
+index which would have been included in the scan had they existed at
+the time. Conceptually, we want to lock the gaps between and
+surrounding index entries within the scanned range.
+
+Correctness requires that any insert into an index generates a
+rw-conflict with a concurrent serializable transaction if, after that
+insert, re-execution of any index scan of the other transaction would
+access the heap for a row not accessed during the previous execution.
+Note that a non-HOT update which expires an old index entry covered
+by the scan and adds a new entry for the modified row's new tuple
+need not generate a conflict, although an update which "moves" a row
+into the scan must generate a conflict. While correctness allows
+false positives, they should be minimized for performance reasons.
+
+Several optimizations are possible:
+
+ * An index scan which is just finding the right position for an
+index insertion or deletion need not acquire a predicate lock.
+
+ * An index scan which is comparing for equality on the entire key
+for a unique index need not acquire a predicate lock as long as a key
+is found corresponding to a visible tuple which has not been modified
+by another transaction -- there are no "between or around" gaps to
+cover.
+
+ * As long as built-in foreign key enforcement continues to use
+its current "special tricks" to deal with MVCC issues, predicate
+locks should not be needed for scans done by enforcement code.
+
+ * If a search determines that no rows can be found regardless of
+index contents because the search conditions are contradictory (e.g.,
+x = 1 AND x = 2), then no predicate lock is needed.
+
+Other index AM implementation considerations:
+
+ * If a btree search discovers that no root page has yet been
+created, a predicate lock on the index relation is required;
+otherwise btree searches must get to the leaf level to determine
+which tuples match, so predicate locks go there.
+
+ * GiST searches can determine that there are no matches at any
+level of the index, so there must be a predicate lock at each index
+level during a GiST search. An index insert at the leaf level can
+then be trusted to ripple up to all levels and locations where
+conflicting predicate locks may exist.
+
+ * The effects of page splits, overflows, consolidations, and
+removals must be carefully reviewed to ensure that predicate locks
+aren't "lost" during those operations, or kept with pages which could
+get re-used for different parts of the index.
+
+
+Innovations
+-----------
+
+The PostgreSQL implementation of Serializable Snapshot Isolation
+differs from what is described in the cited papers for several
+reasons:
+
+ 1. PostgreSQL didn't have any existing predicate locking. It had
+to be added from scratch.
+
+ 2. The existing in-memory lock structures were not suitable for
+tracking SIREAD locks.
+ * The database products used for the prototype
+implementations for the papers used update-in-place with a rollback
+log for their MVCC implementations, while PostgreSQL leaves the old
+version of a row in place and adds a new tuple to represent the row
+at a new location.
+ * In PostgreSQL, tuple level locks are not held in RAM for
+any length of time; lock information is written to the tuples
+involved in the transactions.
+ * In PostgreSQL, existing lock structures have pointers to
+memory which is related to a connection. SIREAD locks need to persist
+past the end of the originating transaction and even the connection
+which ran it.
+ * PostgreSQL needs to be able to tolerate a large number of
+transactions executing while one long-running transaction stays open
+-- the in-RAM techniques discussed in the papers wouldn't support
+that.
+
+ 3. Unlike the database products used for the prototypes described
+in the papers, PostgreSQL didn't already have a true serializable
+isolation level distinct from snapshot isolation.
+
+ 4. PostgreSQL supports subtransactions -- an issue not mentioned
+in the papers.
+
+ 5. PostgreSQL doesn't assign a transaction number to a database
+transaction until and unless necessary.
+
+ 6. PostgreSQL has pluggable data types with user-definable
+operators, as well as pluggable index types, not all of which are
+based around data types which support ordering.
+
+ 7. Some possible optimizations became apparent during development
+and testing.
+
+Differences from the implementation described in the papers are
+listed below.
+
+ * New structures needed to be created in shared memory to track
+the proper information for serializable transactions and their SIREAD
+locks.
+
+ * Because PostgreSQL does not have the same concept of an "oldest
+transaction ID" for all serializable transactions as assumed in the
+Cahill these, we track the oldest snapshot xmin among serializable
+transactions, and a count of how many active transactions use that
+xmin. When the count hits zero we find the new oldest xmin and run a
+clean-up based on that.
+
+ * Because reads in a subtransaction may cause that subtransaction
+to roll back, thereby affecting what is written by the top level
+transaction, predicate locks must survive a subtransaction rollback.
+As a consequence, all xid usage in SSI, including predicate locking,
+is based on the top level xid. When looking at an xid that comes
+from a tuple's xmin or xmax, for example, we always call
+SubTransGetTopmostTransaction() before doing much else with it.
+
+ * Predicate locking in PostgreSQL will start at the tuple level
+when possible, with automatic conversion of multiple fine-grained
+locks to coarser granularity as need to avoid resource exhaustion.
+The amount of memory used for these structures will be configurable,
+to balance RAM usage against SIREAD lock granularity.
+
+ * A process-local copy of locks held by a process and the coarser
+covering locks with counts, are kept to support granularity promotion
+decisions with low CPU and locking overhead.
+
+ * Conflicts will be identified by looking for predicate locks
+when tuples are written and looking at the MVCC information when
+tuples are read. There is no matching between two RAM-based locks.
+
+ * Because write locks are stored in the heap tuples rather than a
+RAM-based lock table, the optimization described in the Cahill thesis
+which eliminates an SIREAD lock where there is a write lock is
+implemented by the following:
+ 1. When checking a heap write for conflicts against existing
+predicate locks, a tuple lock on the tuple being written is removed.
+ 2. When acquiring a predicate lock on a heap tuple, we
+return quickly without doing anything if it is a tuple written by the
+reading transaction.
+
+ * Rather than using conflictIn and conflictOut pointers which use
+NULL to indicate no conflict and a self-reference to indicate
+multiple conflicts or conflicts with committed transactions, we use a
+list of rw-conflicts. With the more complete information, false
+positives are reduced and we have sufficient data for more aggressive
+clean-up and other optimizations.
+ o We can avoid ever rolling back a transaction until and
+unless there is a pivot where a transaction on the conflict *out*
+side of the pivot committed before either of the other transactions.
+ o We can avoid ever rolling back a transaction when the
+transaction on the conflict *in* side of the pivot is explicitly or
+implicitly READ ONLY unless the transaction on the conflict *out*
+side of the pivot committed before the READ ONLY transaction acquired
+its snapshot. (An implicit READ ONLY transaction is one which
+committed without writing, even though it was not explicitly declared
+to be READ ONLY.)
+ o We can more aggressively clean up conflicts, predicate
+locks, and SSI transaction information.
+
+ * Allow a READ ONLY transaction to "opt out" of SSI if there are
+no READ WRITE transactions which could cause the READ ONLY
+transaction to ever become part of a "dangerous structure" of
+overlapping transaction dependencies.
+
+ * Allow the user to request that a READ ONLY transaction wait
+until the conditions are right for it to start in the "opt out" state
+described above. We add a DEFERRABLE state to transactions, which is
+specified and maintained in a way similar to READ ONLY. It is
+ignored for transactions which are not SERIALIZABLE and READ ONLY.
+
+ * When a transaction must be rolled back, we pick among the
+active transactions such that an immediate retry will not fail again
+on conflicts with the same transactions.
+
+ * We use the PostgreSQL SLRU system to hold summarized
+information about older committed transactions to put an upper bound
+on RAM used. Beyond that limit, information spills to disk.
+Performance can degrade in a pessimal situation, but it should be
+tolerable, and transactions won't need to be cancelled or blocked
+from starting.
+
+
+R&D Issues
+----------
+
+This is intended to be the place to record specific issues which need
+more detailed review or analysis.
+
+ * WAL file replay. While serializable implementations using S2PL
+can guarantee that the write-ahead log contains commits in a sequence
+consistent with some serial execution of serializable transactions,
+SSI cannot make that guarantee. While the WAL replay is no less
+consistent than under snapshot isolation, it is possible that under
+PITR recovery or hot standby a database could reach a readable state
+where some transactions appear before other transactions which would
+have had to precede them to maintain serializable consistency. In
+essence, if we do nothing, WAL replay will be at snapshot isolation
+even for serializable transactions. Is this OK? If not, how do we
+address it?
+
+ * External replication. Look at how this impacts external
+replication solutions, like Postgres-R, Slony, pgpool, HS/SR, etc.
+This is related to the "WAL file replay" issue.
+
+ * Weak-memory-ordering machines. Make sure that shared memory
+access which involves visibility across multiple transactions uses
+locks as needed to avoid problems. On the other hand, ensure that we
+really need volatile where we're using it.
+http://archives.postgresql.org/pgsql-committers/2008-06/msg00228.php
+
+ * UNIQUE btree search for equality on all columns. Since a search
+of a UNIQUE index using equality tests on all columns will lock the
+heap tuple if an entry is found, it appears that there is no need to
+get a predicate lock on the index in that case. A predicate lock is
+still needed for such a search if a matching index entry which points
+to a visible tuple is not found.
+
+ * Planner index probes. To avoid problems with data skew at the
+ends of an index which have historically caused bad plans, the
+planner now probes the end of an index to see what the maximum or
+minimum value is when a query appears to be requesting a range of
+data outside what statistics shows is present. These planner checks
+don't require predicate locking, but there's currently no easy way to
+avoid it. What can we do to avoid predicate locking for such planner
+activity?
+
+ * Minimize touching of shared memory. Should lists in shared
+memory push entries which have just been returned to the front of the
+available list, so they will be popped back off soon and some memory
+might never be touched, or should we keep adding returned items to
+the end of the available list?
+
+
+Footnotes
+---------
+
+[1] http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt
+Search for serial execution to find the relevant section.
+
+[2] http://db.cs.berkeley.edu/papers/fntdb07-architecture.pdf
+Joseph M. Hellerstein, Michael Stonebraker and James Hamilton. 2007.
+Architecture of a Database System. Foundations and Trends(R) in
+Databases Vol. 1, No. 2 (2007) 141–259.
+ Of particular interest:
+ * 6.1 A Note on ACID
+ * 6.2 A Brief Review of Serializability
+ * 6.3 Locking and Latching
+ * 6.3.1 Transaction Isolation Levels
+ * 6.5.3 Next-Key Locking: Physical Surrogates for Logical
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 61621a4d0ae..0fe7ce45cd6 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -28,6 +28,7 @@
#include "miscadmin.h"
#include "pg_trace.h"
#include "storage/ipc.h"
+#include "storage/predicate.h"
#include "storage/proc.h"
#include "storage/spin.h"
@@ -178,6 +179,9 @@ NumLWLocks(void)
/* async.c needs one per Async buffer */
numLocks += NUM_ASYNC_BUFFERS;
+ /* predicate.c needs one per old serializable xid buffer */
+ numLocks += NUM_OLDSERXID_BUFFERS;
+
/*
* Add any requested by loadable modules; for backwards-compatibility
* reasons, allocate at least NUM_USER_DEFINED_LWLOCKS of them even if
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
new file mode 100644
index 00000000000..5e62ba9e4d9
--- /dev/null
+++ b/src/backend/storage/lmgr/predicate.c
@@ -0,0 +1,4439 @@
+/*-------------------------------------------------------------------------
+ *
+ * predicate.c
+ * POSTGRES predicate locking
+ * to support full serializable transaction isolation
+ *
+ *
+ * The approach taken is to implement Serializable Snapshot Isolation (SSI)
+ * as initially described in this paper:
+ *
+ * Michael J. Cahill, Uwe Röhm, and Alan D. Fekete. 2008.
+ * Serializable isolation for snapshot databases.
+ * In SIGMOD ’08: Proceedings of the 2008 ACM SIGMOD
+ * international conference on Management of data,
+ * pages 729–738, New York, NY, USA. ACM.
+ * http://doi.acm.org/10.1145/1376616.1376690
+ *
+ * and further elaborated in Cahill's doctoral thesis:
+ *
+ * Michael James Cahill. 2009.
+ * Serializable Isolation for Snapshot Databases.
+ * Sydney Digital Theses.
+ * University of Sydney, School of Information Technologies.
+ * http://hdl.handle.net/2123/5353
+ *
+ *
+ * Predicate locks for Serializable Snapshot Isolation (SSI) are SIREAD
+ * locks, which are so different from normal locks that a distinct set of
+ * structures is required to handle them. They are needed to detect
+ * rw-conflicts when the read happens before the write. (When the write
+ * occurs first, the reading transaction can check for a conflict by
+ * examining the MVCC data.)
+ *
+ * (1) Besides tuples actually read, they must cover ranges of tuples
+ * which would have been read based on the predicate. This will
+ * require modelling the predicates through locks against database
+ * objects such as pages, index ranges, or entire tables.
+ *
+ * (2) They must be kept in RAM for quick access. Because of this, it
+ * isn't possible to always maintain tuple-level granularity -- when
+ * the space allocated to store these approaches exhaustion, a
+ * request for a lock may need to scan for situations where a single
+ * transaction holds many fine-grained locks which can be coalesced
+ * into a single coarser-grained lock.
+ *
+ * (3) They never block anything; they are more like flags than locks
+ * in that regard; although they refer to database objects and are
+ * used to identify rw-conflicts with normal write locks.
+ *
+ * (4) While they are associated with a transaction, they must survive
+ * a successful COMMIT of that transaction, and remain until all
+ * overlapping transactions complete. This even means that they
+ * must survive termination of the transaction's process. If a
+ * top level transaction is rolled back, however, it is immediately
+ * flagged so that it can be ignored, and its SIREAD locks can be
+ * released any time after that.
+ *
+ * (5) The only transactions which create SIREAD locks or check for
+ * conflicts with them are serializable transactions.
+ *
+ * (6) When a write lock for a top level transaction is found to cover
+ * an existing SIREAD lock for the same transaction, the SIREAD lock
+ * can be deleted.
+ *
+ * (7) A write from a serializable transaction must ensure that a xact
+ * record exists for the transaction, with the same lifespan (until
+ * all concurrent transaction complete or the transaction is rolled
+ * back) so that rw-dependencies to that transaction can be
+ * detected.
+ *
+ * We use an optimization for read-only transactions. Under certain
+ * circumstances, a read-only transaction's snapshot can be shown to
+ * never have conflicts with other transactions. This is referred to
+ * as a "safe" snapshot (and one known not to be is "unsafe").
+ * However, it can't be determined whether a snapshot is safe until
+ * all concurrent read/write transactions complete.
+ *
+ * Once a read-only transaction is known to have a safe snapshot, it
+ * can release its predicate locks and exempt itself from further
+ * predicate lock tracking. READ ONLY DEFERRABLE transactions run only
+ * on safe snapshots, waiting as necessary for one to be available.
+ *
+ *
+ * Lightweight locks to manage access to the predicate locking shared
+ * memory objects must be taken in this order, and should be released in
+ * reverse order:
+ *
+ * SerializableFinishedListLock
+ * - Protects the list of transactions which have completed but which
+ * may yet matter because they overlap still-active transactions.
+ *
+ * SerializablePredicateLockListLock
+ * - Protects the linked list of locks held by a transaction. Note
+ * that the locks themselves are also covered by the partition
+ * locks of their respective lock targets; this lock only affects
+ * the linked list connecting the locks related to a transaction.
+ * - All transactions share this single lock (with no partitioning).
+ * - There is never a need for a process other than the one running
+ * an active transaction to walk the list of locks held by that
+ * transaction.
+ * - It is relatively infrequent that another process needs to
+ * modify the list for a transaction, but it does happen for such
+ * things as index page splits for pages with predicate locks and
+ * freeing of predicate locked pages by a vacuum process. When
+ * removing a lock in such cases, the lock itself contains the
+ * pointers needed to remove it from the list. When adding a
+ * lock in such cases, the lock can be added using the anchor in
+ * the transaction structure. Neither requires walking the list.
+ * - Cleaning up the list for a terminated transaction is sometimes
+ * not done on a retail basis, in which case no lock is required.
+ * - Due to the above, a process accessing its active transaction's
+ * list always uses a shared lock, regardless of whether it is
+ * walking or maintaining the list. This improves concurrency
+ * for the common access patterns.
+ * - A process which needs to alter the list of a transaction other
+ * than its own active transaction must acquire an exclusive
+ * lock.
+ *
+ * FirstPredicateLockMgrLock based partition locks
+ * - The same lock protects a target, all locks on that target, and
+ * the linked list of locks on the target..
+ * - When more than one is needed, acquire in ascending order.
+ *
+ * SerializableXactHashLock
+ * - Protects both PredXact and SerializableXidHash.
+ *
+ * PredicateLockNextRowLinkLock
+ * - Protects the priorVersionOfRow and nextVersionOfRow fields of
+ * PREDICATELOCKTARGET when linkage is being created or destroyed.
+ *
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/lmgr/predicate.c
+ *
+ *-------------------------------------------------------------------------
+ */
+/*
+ * INTERFACE ROUTINES
+ *
+ * housekeeping for setting up shared memory predicate lock structures
+ * InitPredicateLocks(void)
+ * PredicateLockShmemSize(void)
+ *
+ * predicate lock reporting
+ * GetPredicateLockStatusData(void)
+ * PageIsPredicateLocked(Relation relation, BlockNumber blkno)
+ *
+ * predicate lock maintenance
+ * RegisterSerializableTransaction(Snapshot snapshot)
+ * RegisterPredicateLockingXid(void)
+ * PredicateLockRelation(Relation relation)
+ * PredicateLockPage(Relation relation, BlockNumber blkno)
+ * PredicateLockTuple(Relation relation, HeapTuple tuple)
+ * PredicateLockPageSplit(Relation relation, BlockNumber oldblkno,
+ * BlockNumber newblkno);
+ * PredicateLockPageCombine(Relation relation, BlockNumber oldblkno,
+ * BlockNumber newblkno);
+ * PredicateLockTupleRowVersionLink(const Relation relation,
+ * const HeapTuple oldTuple,
+ * const HeapTuple newTuple)
+ * ReleasePredicateLocks(bool isCommit)
+ *
+ * conflict detection (may also trigger rollback)
+ * CheckForSerializableConflictOut(bool visible, Relation relation,
+ * HeapTupleData *tup, Buffer buffer)
+ * CheckForSerializableConflictIn(Relation relation, HeapTupleData *tup,
+ * Buffer buffer)
+ *
+ * final rollback checking
+ * PreCommit_CheckForSerializationFailure(void)
+ *
+ * two-phase commit support
+ * AtPrepare_PredicateLocks(void);
+ * PostPrepare_PredicateLocks(TransactionId xid);
+ * PredicateLockTwoPhaseFinish(TransactionId xid, bool isCommit);
+ * predicatelock_twophase_recover(TransactionId xid, uint16 info,
+ * void *recdata, uint32 len);
+ */
+
+#include "postgres.h"
+
+#include "access/slru.h"
+#include "access/subtrans.h"
+#include "access/transam.h"
+#include "access/twophase.h"
+#include "access/twophase_rmgr.h"
+#include "access/xact.h"
+#include "miscadmin.h"
+#include "storage/bufmgr.h"
+#include "storage/predicate.h"
+#include "storage/predicate_internals.h"
+#include "storage/procarray.h"
+#include "utils/rel.h"
+#include "utils/snapmgr.h"
+#include "utils/tqual.h"
+
+/* Uncomment the next line to test the graceful degradation code. */
+/* #define TEST_OLDSERXID */
+
+/*
+ * Test the most selective fields first, for performance.
+ *
+ * a is covered by b if all of the following hold:
+ * 1) a.database = b.database
+ * 2) a.relation = b.relation
+ * 3) b.offset is invalid (b is page-granularity or higher)
+ * 4) either of the following:
+ * 4a) a.offset is valid (a is tuple-granularity) and a.page = b.page
+ * or 4b) a.offset is invalid and b.page is invalid (a is
+ * page-granularity and b is relation-granularity
+ */
+#define TargetTagIsCoveredBy(covered_target, covering_target) \
+ ((GET_PREDICATELOCKTARGETTAG_RELATION(covered_target) == /* (2) */ \
+ GET_PREDICATELOCKTARGETTAG_RELATION(covering_target)) \
+ && (GET_PREDICATELOCKTARGETTAG_OFFSET(covering_target) == \
+ InvalidOffsetNumber) /* (3) */ \
+ && (((GET_PREDICATELOCKTARGETTAG_OFFSET(covered_target) != \
+ InvalidOffsetNumber) /* (4a) */ \
+ && (GET_PREDICATELOCKTARGETTAG_PAGE(covering_target) == \
+ GET_PREDICATELOCKTARGETTAG_PAGE(covered_target))) \
+ || ((GET_PREDICATELOCKTARGETTAG_PAGE(covering_target) == \
+ InvalidBlockNumber) /* (4b) */ \
+ && (GET_PREDICATELOCKTARGETTAG_PAGE(covered_target) \
+ != InvalidBlockNumber))) \
+ && (GET_PREDICATELOCKTARGETTAG_DB(covered_target) == /* (1) */ \
+ GET_PREDICATELOCKTARGETTAG_DB(covering_target)))
+
+/*
+ * The predicate locking target and lock shared hash tables are partitioned to
+ * reduce contention. To determine which partition a given target belongs to,
+ * compute the tag's hash code with PredicateLockTargetTagHashCode(), then
+ * apply one of these macros.
+ * NB: NUM_PREDICATELOCK_PARTITIONS must be a power of 2!
+ */
+#define PredicateLockHashPartition(hashcode) \
+ ((hashcode) % NUM_PREDICATELOCK_PARTITIONS)
+#define PredicateLockHashPartitionLock(hashcode) \
+ ((LWLockId) (FirstPredicateLockMgrLock + PredicateLockHashPartition(hashcode)))
+
+#define NPREDICATELOCKTARGETENTS() \
+ mul_size(max_predicate_locks_per_xact, add_size(MaxBackends, max_prepared_xacts))
+
+#define SxactIsOnFinishedList(sxact) (!SHMQueueIsDetached(&((sxact)->finishedLink)))
+
+#define SxactIsPrepared(sxact) (((sxact)->flags & SXACT_FLAG_PREPARED) != 0)
+#define SxactIsCommitted(sxact) (((sxact)->flags & SXACT_FLAG_COMMITTED) != 0)
+#define SxactIsRolledBack(sxact) (((sxact)->flags & SXACT_FLAG_ROLLED_BACK) != 0)
+#define SxactIsReadOnly(sxact) (((sxact)->flags & SXACT_FLAG_READ_ONLY) != 0)
+#define SxactHasSummaryConflictIn(sxact) (((sxact)->flags & SXACT_FLAG_SUMMARY_CONFLICT_IN) != 0)
+#define SxactHasSummaryConflictOut(sxact) (((sxact)->flags & SXACT_FLAG_SUMMARY_CONFLICT_OUT) != 0)
+#define SxactHasConflictOut(sxact) (((sxact)->flags & SXACT_FLAG_CONFLICT_OUT) != 0)
+#define SxactIsDeferrableWaiting(sxact) (((sxact)->flags & SXACT_FLAG_DEFERRABLE_WAITING) != 0)
+#define SxactIsROSafe(sxact) (((sxact)->flags & SXACT_FLAG_RO_SAFE) != 0)
+#define SxactIsROUnsafe(sxact) (((sxact)->flags & SXACT_FLAG_RO_UNSAFE) != 0)
+#define SxactIsMarkedForDeath(sxact) (((sxact)->flags & SXACT_FLAG_MARKED_FOR_DEATH) != 0)
+
+/*
+ * When a public interface method is called for a split on an index relation,
+ * this is the test to see if we should do a quick return.
+ */
+#define SkipSplitTracking(relation) \
+ (((relation)->rd_id < FirstBootstrapObjectId) \
+ || RelationUsesLocalBuffers(relation))
+
+/*
+ * When a public interface method is called for serializing a relation within
+ * the current transaction, this is the test to see if we should do a quick
+ * return.
+ */
+#define SkipSerialization(relation) \
+ ((!IsolationIsSerializable()) \
+ || ((MySerializableXact == InvalidSerializableXact)) \
+ || ReleasePredicateLocksIfROSafe() \
+ || SkipSplitTracking(relation))
+
+
+/*
+ * Compute the hash code associated with a PREDICATELOCKTARGETTAG.
+ *
+ * To avoid unnecessary recomputations of the hash code, we try to do this
+ * just once per function, and then pass it around as needed. Aside from
+ * passing the hashcode to hash_search_with_hash_value(), we can extract
+ * the lock partition number from the hashcode.
+ */
+#define PredicateLockTargetTagHashCode(predicatelocktargettag) \
+ (tag_hash((predicatelocktargettag), sizeof(PREDICATELOCKTARGETTAG)))
+
+/*
+ * Given a predicate lock tag, and the hash for its target,
+ * compute the lock hash.
+ *
+ * To make the hash code also depend on the transaction, we xor the sxid
+ * struct's address into the hash code, left-shifted so that the
+ * partition-number bits don't change. Since this is only a hash, we
+ * don't care if we lose high-order bits of the address; use an
+ * intermediate variable to suppress cast-pointer-to-int warnings.
+ */
+#define PredicateLockHashCodeFromTargetHashCode(predicatelocktag, targethash) \
+ ((targethash) ^ ((uint32) PointerGetDatum((predicatelocktag)->myXact)) \
+ << LOG2_NUM_PREDICATELOCK_PARTITIONS)
+
+
+/*
+ * The SLRU buffer area through which we access the old xids.
+ */
+static SlruCtlData OldSerXidSlruCtlData;
+
+#define OldSerXidSlruCtl (&OldSerXidSlruCtlData)
+
+#define OLDSERXID_PAGESIZE BLCKSZ
+#define OLDSERXID_ENTRYSIZE sizeof(SerCommitSeqNo)
+#define OLDSERXID_ENTRIESPERPAGE (OLDSERXID_PAGESIZE / OLDSERXID_ENTRYSIZE)
+#define OLDSERXID_MAX_PAGE (SLRU_PAGES_PER_SEGMENT * 0x10000 - 1)
+
+#define OldSerXidNextPage(page) (((page) >= OLDSERXID_MAX_PAGE) ? 0 : (page) + 1)
+
+#define OldSerXidValue(slotno, xid) (*((SerCommitSeqNo *) \
+ (OldSerXidSlruCtl->shared->page_buffer[slotno] + \
+ ((((uint32) (xid)) % OLDSERXID_ENTRIESPERPAGE) * OLDSERXID_ENTRYSIZE))))
+
+#define OldSerXidPage(xid) ((((uint32) (xid)) / OLDSERXID_ENTRIESPERPAGE) % (OLDSERXID_MAX_PAGE + 1))
+#define OldSerXidSegment(page) ((page) / SLRU_PAGES_PER_SEGMENT)
+
+typedef struct OldSerXidControlData
+{
+ int headPage;
+ int tailSegment;
+ TransactionId headXid;
+ TransactionId tailXid;
+ bool warningIssued;
+} OldSerXidControlData;
+
+typedef struct OldSerXidControlData *OldSerXidControl;
+
+static OldSerXidControl oldSerXidControl;
+
+/*
+ * When the oldest committed transaction on the "finished" list is moved to
+ * SLRU, its predicate locks will be moved to this "dummy" transaction,
+ * collapsing duplicate targets. When a duplicate is found, the later
+ * commitSeqNo is used.
+ */
+static SERIALIZABLEXACT *OldCommittedSxact;
+
+
+/* This configuration variable is used to set the predicate lock table size */
+int max_predicate_locks_per_xact; /* set by guc.c */
+
+/*
+ * This provides a list of objects in order to track transactions
+ * participating in predicate locking. Entries in the list are fixed size,
+ * and reside in shared memory. The memory address of an entry must remain
+ * fixed during its lifetime. The list will be protected from concurrent
+ * update externally; no provision is made in this code to manage that. The
+ * number of entries in the list, and the size allowed for each entry is
+ * fixed upon creation.
+ */
+static PredXactList PredXact;
+
+/*
+ * This provides a pool of RWConflict data elements to use in conflict lists
+ * between transactions.
+ */
+static RWConflictPoolHeader RWConflictPool;
+
+/*
+ * The predicate locking hash tables are in shared memory.
+ * Each backend keeps pointers to them.
+ */
+static HTAB *SerializableXidHash;
+static HTAB *PredicateLockTargetHash;
+static HTAB *PredicateLockHash;
+static SHM_QUEUE *FinishedSerializableTransactions;
+
+/*
+ * Tag for a reserved entry in PredicateLockTargetHash; used to ensure
+ * there's an element available for scratch space if we need it,
+ * e.g. in PredicateLockPageSplit. This is an otherwise-invalid tag.
+ */
+static const PREDICATELOCKTARGETTAG ReservedTargetTag = {0, 0, 0, 0, 0};
+
+/*
+ * The local hash table used to determine when to combine multiple fine-
+ * grained locks into a single courser-grained lock.
+ */
+static HTAB *LocalPredicateLockHash = NULL;
+
+/*
+ * Keep a pointer to the currently-running serializable transaction (if any)
+ * for quick reference.
+ * TODO SSI: Remove volatile qualifier and the then-unnecessary casts?
+ */
+static volatile SERIALIZABLEXACT *MySerializableXact = InvalidSerializableXact;
+
+/* local functions */
+
+static SERIALIZABLEXACT *CreatePredXact(void);
+static void ReleasePredXact(SERIALIZABLEXACT *sxact);
+static SERIALIZABLEXACT *FirstPredXact(void);
+static SERIALIZABLEXACT *NextPredXact(SERIALIZABLEXACT *sxact);
+
+static bool RWConflictExists(const SERIALIZABLEXACT *reader, const SERIALIZABLEXACT *writer);
+static void SetRWConflict(SERIALIZABLEXACT *reader, SERIALIZABLEXACT *writer);
+static void SetPossibleUnsafeConflict(SERIALIZABLEXACT *roXact, SERIALIZABLEXACT *activeXact);
+static void ReleaseRWConflict(RWConflict conflict);
+static void FlagSxactUnsafe(SERIALIZABLEXACT *sxact);
+
+static bool OldSerXidPagePrecedesLogically(int p, int q);
+static void OldSerXidInit(void);
+static void OldSerXidAdd(TransactionId xid, SerCommitSeqNo minConflictCommitSeqNo);
+static SerCommitSeqNo OldSerXidGetMinConflictCommitSeqNo(TransactionId xid);
+static void OldSerXidSetActiveSerXmin(TransactionId xid);
+
+static uint32 predicatelock_hash(const void *key, Size keysize);
+static void SummarizeOldestCommittedSxact(void);
+static Snapshot GetSafeSnapshot(Snapshot snapshot);
+static Snapshot RegisterSerializableTransactionInt(Snapshot snapshot);
+static bool PredicateLockExists(const PREDICATELOCKTARGETTAG *targettag);
+static bool GetParentPredicateLockTag(const PREDICATELOCKTARGETTAG *tag,
+ PREDICATELOCKTARGETTAG *parent);
+static bool CoarserLockCovers(const PREDICATELOCKTARGETTAG *newtargettag);
+static void RemoveTargetIfNoLongerUsed(PREDICATELOCKTARGET *target,
+ uint32 targettaghash);
+static void DeleteChildTargetLocks(const PREDICATELOCKTARGETTAG *newtargettag);
+static int PredicateLockPromotionThreshold(const PREDICATELOCKTARGETTAG *tag);
+static bool CheckAndPromotePredicateLockRequest(const PREDICATELOCKTARGETTAG *reqtag);
+static void DecrementParentLocks(const PREDICATELOCKTARGETTAG *targettag);
+static void CreatePredicateLock(const PREDICATELOCKTARGETTAG *targettag,
+ uint32 targettaghash,
+ SERIALIZABLEXACT *sxact);
+static void DeleteLockTarget(PREDICATELOCKTARGET *target, uint32 targettaghash);
+static bool TransferPredicateLocksToNewTarget(const PREDICATELOCKTARGETTAG oldtargettag,
+ const PREDICATELOCKTARGETTAG newtargettag,
+ bool removeOld);
+static void PredicateLockAcquire(const PREDICATELOCKTARGETTAG *targettag);
+static void SetNewSxactGlobalXmin(void);
+static bool ReleasePredicateLocksIfROSafe(void);
+static void ClearOldPredicateLocks(void);
+static void ReleaseOneSerializableXact(SERIALIZABLEXACT *sxact, bool partial,
+ bool summarize);
+static bool XidIsConcurrent(TransactionId xid);
+static void CheckTargetForConflictsIn(PREDICATELOCKTARGETTAG *targettag);
+static bool CheckSingleTargetForConflictsIn(PREDICATELOCKTARGETTAG *targettag,
+ PREDICATELOCKTARGETTAG *nexttargettag);
+static void FlagRWConflict(SERIALIZABLEXACT *reader, SERIALIZABLEXACT *writer);
+static void OnConflict_CheckForSerializationFailure(const SERIALIZABLEXACT *reader,
+ SERIALIZABLEXACT *writer);
+
+/*------------------------------------------------------------------------*/
+
+/*
+ * These functions are a simple implementation of a list for this specific
+ * type of struct. If there is ever a generalized shared memory list, we
+ * should probably switch to that.
+ */
+static SERIALIZABLEXACT *
+CreatePredXact(void)
+{
+ PredXactListElement ptle;
+
+ ptle = (PredXactListElement)
+ SHMQueueNext(&PredXact->availableList,
+ &PredXact->availableList,
+ offsetof(PredXactListElementData, link));
+ if (!ptle)
+ return NULL;
+
+ SHMQueueDelete(&ptle->link);
+ SHMQueueInsertBefore(&PredXact->activeList, &ptle->link);
+ return &ptle->sxact;
+}
+
+static void
+ReleasePredXact(SERIALIZABLEXACT *sxact)
+{
+ PredXactListElement ptle;
+
+ Assert(ShmemAddrIsValid(sxact));
+
+ ptle = (PredXactListElement)
+ (((char *) sxact)
+ - offsetof(PredXactListElementData, sxact)
+ +offsetof(PredXactListElementData, link));
+ SHMQueueDelete(&ptle->link);
+ SHMQueueInsertBefore(&PredXact->availableList, &ptle->link);
+}
+
+static SERIALIZABLEXACT *
+FirstPredXact(void)
+{
+ PredXactListElement ptle;
+
+ ptle = (PredXactListElement)
+ SHMQueueNext(&PredXact->activeList,
+ &PredXact->activeList,
+ offsetof(PredXactListElementData, link));
+ if (!ptle)
+ return NULL;
+
+ return &ptle->sxact;
+}
+
+static SERIALIZABLEXACT *
+NextPredXact(SERIALIZABLEXACT *sxact)
+{
+ PredXactListElement ptle;
+
+ Assert(ShmemAddrIsValid(sxact));
+
+ ptle = (PredXactListElement)
+ (((char *) sxact)
+ - offsetof(PredXactListElementData, sxact)
+ +offsetof(PredXactListElementData, link));
+ ptle = (PredXactListElement)
+ SHMQueueNext(&PredXact->activeList,
+ &ptle->link,
+ offsetof(PredXactListElementData, link));
+ if (!ptle)
+ return NULL;
+
+ return &ptle->sxact;
+}
+
+/*------------------------------------------------------------------------*/
+
+/*
+ * These functions manage primitive access to the RWConflict pool and lists.
+ */
+static bool
+RWConflictExists(const SERIALIZABLEXACT *reader, const SERIALIZABLEXACT *writer)
+{
+ RWConflict conflict;
+
+ Assert(reader != writer);
+
+ /* Check the ends of the purported conflict first. */
+ if (SxactIsRolledBack(reader)
+ || SxactIsRolledBack(writer)
+ || SHMQueueEmpty(&reader->outConflicts)
+ || SHMQueueEmpty(&writer->inConflicts))
+ return false;
+
+ /* A conflict is possible; walk the list to find out. */
+ conflict = (RWConflict)
+ SHMQueueNext(&reader->outConflicts,
+ &reader->outConflicts,
+ offsetof(RWConflictData, outLink));
+ while (conflict)
+ {
+ if (conflict->sxactIn == writer)
+ return true;
+ conflict = (RWConflict)
+ SHMQueueNext(&reader->outConflicts,
+ &conflict->outLink,
+ offsetof(RWConflictData, outLink));
+ }
+
+ /* No conflict found. */
+ return false;
+}
+
+static void
+SetRWConflict(SERIALIZABLEXACT *reader, SERIALIZABLEXACT *writer)
+{
+ RWConflict conflict;
+
+ Assert(reader != writer);
+ Assert(!RWConflictExists(reader, writer));
+
+ conflict = (RWConflict)
+ SHMQueueNext(&RWConflictPool->availableList,
+ &RWConflictPool->availableList,
+ offsetof(RWConflictData, outLink));
+ if (!conflict)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("not enough elements in RWConflictPool to record a rw-conflict"),
+ errhint("You might need to run fewer transactions at a time or increase max_connections.")));
+
+ SHMQueueDelete(&conflict->outLink);
+
+ conflict->sxactOut = reader;
+ conflict->sxactIn = writer;
+ SHMQueueInsertBefore(&reader->outConflicts, &conflict->outLink);
+ SHMQueueInsertBefore(&writer->inConflicts, &conflict->inLink);
+}
+
+static void
+SetPossibleUnsafeConflict(SERIALIZABLEXACT *roXact,
+ SERIALIZABLEXACT *activeXact)
+{
+ RWConflict conflict;
+
+ Assert(roXact != activeXact);
+ Assert(SxactIsReadOnly(roXact));
+ Assert(!SxactIsReadOnly(activeXact));
+
+ conflict = (RWConflict)
+ SHMQueueNext(&RWConflictPool->availableList,
+ &RWConflictPool->availableList,
+ offsetof(RWConflictData, outLink));
+ if (!conflict)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("not enough elements in RWConflictPool to record a potential rw-conflict"),
+ errhint("You might need to run fewer transactions at a time or increase max_connections.")));
+
+ SHMQueueDelete(&conflict->outLink);
+
+ conflict->sxactOut = activeXact;
+ conflict->sxactIn = roXact;
+ SHMQueueInsertBefore(&activeXact->possibleUnsafeConflicts,
+ &conflict->outLink);
+ SHMQueueInsertBefore(&roXact->possibleUnsafeConflicts,
+ &conflict->inLink);
+}
+
+static void
+ReleaseRWConflict(RWConflict conflict)
+{
+ SHMQueueDelete(&conflict->inLink);
+ SHMQueueDelete(&conflict->outLink);
+ SHMQueueInsertBefore(&RWConflictPool->availableList, &conflict->outLink);
+}
+
+static void
+FlagSxactUnsafe(SERIALIZABLEXACT *sxact)
+{
+ RWConflict conflict,
+ nextConflict;
+
+ Assert(SxactIsReadOnly(sxact));
+ Assert(!SxactIsROSafe(sxact));
+
+ sxact->flags |= SXACT_FLAG_RO_UNSAFE;
+
+ /*
+ * We know this isn't a safe snapshot, so we can stop looking for other
+ * potential conflicts.
+ */
+ conflict = (RWConflict)
+ SHMQueueNext(&sxact->possibleUnsafeConflicts,
+ &sxact->possibleUnsafeConflicts,
+ offsetof(RWConflictData, inLink));
+ while (conflict)
+ {
+ nextConflict = (RWConflict)
+ SHMQueueNext(&sxact->possibleUnsafeConflicts,
+ &conflict->inLink,
+ offsetof(RWConflictData, inLink));
+
+ Assert(!SxactIsReadOnly(conflict->sxactOut));
+ Assert(sxact == conflict->sxactIn);
+
+ ReleaseRWConflict(conflict);
+
+ conflict = nextConflict;
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
+/*
+ * We will work on the page range of 0..OLDSERXID_MAX_PAGE.
+ * Compares using wraparound logic, as is required by slru.c.
+ */
+static bool
+OldSerXidPagePrecedesLogically(int p, int q)
+{
+ int diff;
+
+ /*
+ * We have to compare modulo (OLDSERXID_MAX_PAGE+1)/2. Both inputs should
+ * be in the range 0..OLDSERXID_MAX_PAGE.
+ */
+ Assert(p >= 0 && p <= OLDSERXID_MAX_PAGE);
+ Assert(q >= 0 && q <= OLDSERXID_MAX_PAGE);
+
+ diff = p - q;
+ if (diff >= ((OLDSERXID_MAX_PAGE + 1) / 2))
+ diff -= OLDSERXID_MAX_PAGE + 1;
+ else if (diff < -((OLDSERXID_MAX_PAGE + 1) / 2))
+ diff += OLDSERXID_MAX_PAGE + 1;
+ return diff < 0;
+}
+
+/*
+ * Initialize for the tracking of old serializable committed xids.
+ */
+static void
+OldSerXidInit(void)
+{
+ bool found;
+
+ /*
+ * Set up SLRU management of the pg_serial data.
+ */
+ OldSerXidSlruCtl->PagePrecedes = OldSerXidPagePrecedesLogically;
+ SimpleLruInit(OldSerXidSlruCtl, "OldSerXid SLRU Ctl", NUM_OLDSERXID_BUFFERS, 0,
+ OldSerXidLock, "pg_serial");
+ /* Override default assumption that writes should be fsync'd */
+ OldSerXidSlruCtl->do_fsync = false;
+
+ /*
+ * Create or attach to the OldSerXidControl structure.
+ */
+ oldSerXidControl = (OldSerXidControl)
+ ShmemInitStruct("OldSerXidControlData", sizeof(OldSerXidControlData), &found);
+
+ if (!found)
+ {
+ /*
+ * Set control information to reflect empty SLRU.
+ */
+ oldSerXidControl->headPage = -1;
+ oldSerXidControl->tailSegment = -1;
+ oldSerXidControl->headXid = InvalidTransactionId;
+ oldSerXidControl->tailXid = InvalidTransactionId;
+ oldSerXidControl->warningIssued = false;
+ }
+}
+
+/*
+ * Record a committed read write serializable xid and the minimum
+ * commitSeqNo of any transactions to which this xid had a rw-conflict out.
+ * A zero seqNo means that there were no conflicts out from xid.
+ *
+ * The return value is normally false -- true means that we're about to
+ * wrap around our space for tracking these xids, so the caller might want
+ * to take action to prevent that.
+ */
+static void
+OldSerXidAdd(TransactionId xid, SerCommitSeqNo minConflictCommitSeqNo)
+{
+ TransactionId tailXid;
+ int targetPage;
+ int slotno;
+ int page;
+ int xidSpread;
+ bool isNewPage;
+
+ Assert(TransactionIdIsValid(xid));
+
+ targetPage = OldSerXidPage(xid);
+
+ LWLockAcquire(OldSerXidLock, LW_EXCLUSIVE);
+
+ /*
+ * If no serializable transactions are active, there shouldn't be anything
+ * to push out to this SLRU. Hitting this assert would mean there's
+ * something wrong with the earlier cleanup logic.
+ */
+ tailXid = oldSerXidControl->tailXid;
+ Assert(TransactionIdIsValid(tailXid));
+
+ if (oldSerXidControl->headPage < 0)
+ {
+ page = OldSerXidPage(tailXid);
+ oldSerXidControl->tailSegment = OldSerXidSegment(page);
+ page = oldSerXidControl->tailSegment * OLDSERXID_ENTRIESPERPAGE;
+ isNewPage = true;
+ }
+ else
+ {
+ page = OldSerXidNextPage(oldSerXidControl->headPage);
+ isNewPage = OldSerXidPagePrecedesLogically(oldSerXidControl->headPage, targetPage);
+ }
+
+ if (!TransactionIdIsValid(oldSerXidControl->headXid)
+ || TransactionIdFollows(xid, oldSerXidControl->headXid))
+ oldSerXidControl->headXid = xid;
+ if (oldSerXidControl->headPage < 0
+ || OldSerXidPagePrecedesLogically(oldSerXidControl->headPage, targetPage))
+ oldSerXidControl->headPage = targetPage;
+
+ xidSpread = (((uint32) xid) - ((uint32) tailXid));
+ if (oldSerXidControl->warningIssued)
+ {
+ if (xidSpread < 800000000)
+ oldSerXidControl->warningIssued = false;
+ }
+ else if (xidSpread >= 1000000000)
+ {
+ oldSerXidControl->warningIssued = true;
+ ereport(WARNING,
+ (errmsg("memory for serializable conflict tracking is nearly exhausted"),
+ errhint("There may be an idle transaction or a forgotten prepared transaction causing this.")));
+ }
+
+ if (isNewPage)
+ {
+ /* Initialize intervening pages. */
+ while (page != targetPage)
+ {
+ (void) SimpleLruZeroPage(OldSerXidSlruCtl, page);
+ page = OldSerXidNextPage(page);
+ }
+ slotno = SimpleLruZeroPage(OldSerXidSlruCtl, targetPage);
+ }
+ else
+ slotno = SimpleLruReadPage(OldSerXidSlruCtl, targetPage, true, xid);
+
+ OldSerXidValue(slotno, xid) = minConflictCommitSeqNo;
+
+ LWLockRelease(OldSerXidLock);
+}
+
+/*
+ * Get the minimum commitSeqNo for any conflict out for the given xid. For
+ * a transaction which exists but has no conflict out, InvalidSerCommitSeqNo
+ * will be returned.
+ */
+static SerCommitSeqNo
+OldSerXidGetMinConflictCommitSeqNo(TransactionId xid)
+{
+ TransactionId headXid;
+ TransactionId tailXid;
+ SerCommitSeqNo val;
+ int slotno;
+
+ Assert(TransactionIdIsValid(xid));
+
+ LWLockAcquire(OldSerXidLock, LW_SHARED);
+ headXid = oldSerXidControl->headXid;
+ tailXid = oldSerXidControl->tailXid;
+ LWLockRelease(OldSerXidLock);
+
+ if (!TransactionIdIsValid(headXid))
+ return 0;
+
+ Assert(TransactionIdIsValid(tailXid));
+
+ if (TransactionIdPrecedes(xid, tailXid)
+ || TransactionIdFollows(xid, headXid))
+ return 0;
+
+ /*
+ * The following function must be called without holding OldSerXidLock,
+ * but will return with that lock held, which must then be released.
+ */
+ slotno = SimpleLruReadPage_ReadOnly(OldSerXidSlruCtl,
+ OldSerXidPage(xid), xid);
+ val = OldSerXidValue(slotno, xid);
+ LWLockRelease(OldSerXidLock);
+ return val;
+}
+
+/*
+ * Call this whenever there is a new xmin for active serializable
+ * transactions. We don't need to keep information on transactions which
+ * preceed that. InvalidTransactionId means none active, so everything in
+ * the SLRU should be discarded.
+ */
+static void
+OldSerXidSetActiveSerXmin(TransactionId xid)
+{
+ int newTailPage;
+ int newTailSegment;
+
+ LWLockAcquire(OldSerXidLock, LW_EXCLUSIVE);
+
+ /*
+ * When no sxacts are active, nothing overlaps, set the xid values to
+ * invalid to show that there are no valid entries. Don't clear the
+ * segment/page information, though. A new xmin might still land in an
+ * existing segment, and we don't want to repeatedly delete and re-create
+ * the same segment file.
+ */
+ if (!TransactionIdIsValid(xid))
+ {
+ if (TransactionIdIsValid(oldSerXidControl->tailXid))
+ {
+ oldSerXidControl->headXid = InvalidTransactionId;
+ oldSerXidControl->tailXid = InvalidTransactionId;
+ }
+ LWLockRelease(OldSerXidLock);
+ return;
+ }
+
+ /*
+ * When we're recovering prepared transactions, the global xmin might move
+ * backwards depending on the order they're recovered. Normally that's not
+ * OK, but during recovery no serializable transactions will commit, so
+ * the SLRU is empty and we can get away with it.
+ */
+ if (RecoveryInProgress())
+ {
+ Assert(oldSerXidControl->headPage < 0);
+ if (!TransactionIdIsValid(oldSerXidControl->tailXid)
+ || TransactionIdPrecedes(xid, oldSerXidControl->tailXid))
+ oldSerXidControl->tailXid = xid;
+ LWLockRelease(OldSerXidLock);
+ return;
+ }
+
+ Assert(!TransactionIdIsValid(oldSerXidControl->tailXid)
+ || TransactionIdFollows(xid, oldSerXidControl->tailXid));
+
+ oldSerXidControl->tailXid = xid;
+
+ /* Exit quickly if there are no segments active. */
+ if (oldSerXidControl->headPage < 0)
+ {
+ LWLockRelease(OldSerXidLock);
+ return;
+ }
+
+ newTailPage = OldSerXidPage(xid);
+ newTailSegment = OldSerXidSegment(newTailPage);
+
+ /* Exit quickly if we're still on the same segment. */
+ if (newTailSegment == oldSerXidControl->tailSegment)
+ {
+ LWLockRelease(OldSerXidLock);
+ return;
+ }
+
+ oldSerXidControl->tailSegment = newTailSegment;
+
+ /* See if that has cleared the last segment. */
+ if (OldSerXidPagePrecedesLogically(oldSerXidControl->headPage,
+ newTailSegment * SLRU_PAGES_PER_SEGMENT))
+ {
+ oldSerXidControl->headXid = InvalidTransactionId;
+ oldSerXidControl->headPage = -1;
+ oldSerXidControl->tailSegment = -1;
+ }
+
+ LWLockRelease(OldSerXidLock);
+
+ SimpleLruTruncate(OldSerXidSlruCtl, newTailPage);
+}
+
+/*------------------------------------------------------------------------*/
+
+/*
+ * InitPredicateLocks -- Initialize the predicate locking data structures.
+ *
+ * This is called from CreateSharedMemoryAndSemaphores(), which see for
+ * more comments. In the normal postmaster case, the shared hash tables
+ * are created here. Backends inherit the pointers
+ * to the shared tables via fork(). In the EXEC_BACKEND case, each
+ * backend re-executes this code to obtain pointers to the already existing
+ * shared hash tables.
+ */
+void
+InitPredicateLocks(void)
+{
+ HASHCTL info;
+ int hash_flags;
+ long init_table_size,
+ max_table_size;
+ Size requestSize;
+ bool found;
+
+ /*
+ * Compute init/max size to request for predicate lock target hashtable.
+ * Note these calculations must agree with PredicateLockShmemSize!
+ */
+ max_table_size = NPREDICATELOCKTARGETENTS();
+ init_table_size = max_table_size / 2;
+
+ /*
+ * Allocate hash table for PREDICATELOCKTARGET structs. This stores
+ * per-predicate-lock-target information.
+ */
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(PREDICATELOCKTARGETTAG);
+ info.entrysize = sizeof(PREDICATELOCKTARGET);
+ info.hash = tag_hash;
+ info.num_partitions = NUM_PREDICATELOCK_PARTITIONS;
+ hash_flags = (HASH_ELEM | HASH_FUNCTION | HASH_PARTITION);
+
+ PredicateLockTargetHash = ShmemInitHash("PREDICATELOCKTARGET hash",
+ init_table_size,
+ max_table_size,
+ &info,
+ hash_flags);
+
+ /* Assume an average of 2 xacts per target */
+ max_table_size *= 2;
+ init_table_size *= 2;
+
+ /*
+ * Reserve an entry in the hash table; we use it to make sure there's
+ * always one entry available when we need to split or combine a page,
+ * because running out of space there could mean aborting a
+ * non-serializable transaction.
+ */
+ hash_search(PredicateLockTargetHash, &ReservedTargetTag,
+ HASH_ENTER, NULL);
+
+
+ /*
+ * Allocate hash table for PREDICATELOCK structs. This stores per
+ * xact-lock-of-a-target information.
+ */
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(PREDICATELOCKTAG);
+ info.entrysize = sizeof(PREDICATELOCK);
+ info.hash = predicatelock_hash;
+ info.num_partitions = NUM_PREDICATELOCK_PARTITIONS;
+ hash_flags = (HASH_ELEM | HASH_FUNCTION | HASH_PARTITION);
+
+ PredicateLockHash = ShmemInitHash("PREDICATELOCK hash",
+ init_table_size,
+ max_table_size,
+ &info,
+ hash_flags);
+
+ /*
+ * Compute init/max size to request for serializable transaction
+ * hashtable. Note these calculations must agree with
+ * PredicateLockShmemSize!
+ */
+ max_table_size = (MaxBackends + max_prepared_xacts);
+ init_table_size = max_table_size / 2;
+
+ /*
+ * Allocate a list to hold information on transactions participating in
+ * predicate locking.
+ *
+ * Assume an average of 10 predicate locking transactions per backend.
+ * This allows aggressive cleanup while detail is present before data must
+ * be summarized for storage in SLRU and the "dummy" transaction.
+ */
+ max_table_size *= 10;
+ init_table_size *= 10;
+
+ PredXact = ShmemInitStruct("PredXactList",
+ PredXactListDataSize,
+ &found);
+ if (!found)
+ {
+ int i;
+
+ SHMQueueInit(&PredXact->availableList);
+ SHMQueueInit(&PredXact->activeList);
+ PredXact->SxactGlobalXmin = InvalidTransactionId;
+ PredXact->SxactGlobalXminCount = 0;
+ PredXact->WritableSxactCount = 0;
+ PredXact->LastSxactCommitSeqNo = FirstNormalSerCommitSeqNo - 1;
+ PredXact->CanPartialClearThrough = 0;
+ PredXact->HavePartialClearedThrough = 0;
+ PredXact->NeedTargetLinkCleanup = false;
+ requestSize = mul_size((Size) max_table_size,
+ PredXactListElementDataSize);
+ PredXact->element = ShmemAlloc(requestSize);
+ if (PredXact->element == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("not enough shared memory for elements of data structure"
+ " \"%s\" (%lu bytes requested)",
+ "PredXactList", (unsigned long) requestSize)));
+ /* Add all elements to available list, clean. */
+ memset(PredXact->element, 0, requestSize);
+ for (i = 0; i < max_table_size; i++)
+ {
+ SHMQueueInsertBefore(&(PredXact->availableList),
+ &(PredXact->element[i].link));
+ }
+ PredXact->OldCommittedSxact = CreatePredXact();
+ SetInvalidVirtualTransactionId(PredXact->OldCommittedSxact->vxid);
+ PredXact->OldCommittedSxact->commitSeqNo = 0;
+ PredXact->OldCommittedSxact->SeqNo.lastCommitBeforeSnapshot = 0;
+ SHMQueueInit(&PredXact->OldCommittedSxact->outConflicts);
+ SHMQueueInit(&PredXact->OldCommittedSxact->inConflicts);
+ SHMQueueInit(&PredXact->OldCommittedSxact->predicateLocks);
+ SHMQueueInit(&PredXact->OldCommittedSxact->finishedLink);
+ SHMQueueInit(&PredXact->OldCommittedSxact->possibleUnsafeConflicts);
+ PredXact->OldCommittedSxact->topXid = InvalidTransactionId;
+ PredXact->OldCommittedSxact->finishedBefore = InvalidTransactionId;
+ PredXact->OldCommittedSxact->xmin = InvalidTransactionId;
+ PredXact->OldCommittedSxact->flags = SXACT_FLAG_COMMITTED;
+ PredXact->OldCommittedSxact->pid = 0;
+ }
+ /* This never changes, so let's keep a local copy. */
+ OldCommittedSxact = PredXact->OldCommittedSxact;
+
+ /*
+ * Allocate hash table for SERIALIZABLEXID structs. This stores per-xid
+ * information for serializable transactions which have accessed data.
+ */
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(SERIALIZABLEXIDTAG);
+ info.entrysize = sizeof(SERIALIZABLEXID);
+ info.hash = tag_hash;
+ hash_flags = (HASH_ELEM | HASH_FUNCTION);
+
+ SerializableXidHash = ShmemInitHash("SERIALIZABLEXID hash",
+ init_table_size,
+ max_table_size,
+ &info,
+ hash_flags);
+
+ /*
+ * Allocate space for tracking rw-conflicts in lists attached to the
+ * transactions.
+ *
+ * Assume an average of 5 conflicts per transaction. Calculations suggest
+ * that this will prevent resource exhaustion in even the most pessimal
+ * loads up to max_connections = 200 with all 200 connections pounding the
+ * database with serializable transactions. Beyond that, there may be
+ * occassional transactions canceled when trying to flag conflicts. That's
+ * probably OK.
+ */
+ max_table_size *= 5;
+
+ RWConflictPool = ShmemInitStruct("RWConflictPool",
+ RWConflictPoolHeaderDataSize,
+ &found);
+ if (!found)
+ {
+ int i;
+
+ SHMQueueInit(&RWConflictPool->availableList);
+ requestSize = mul_size((Size) max_table_size,
+ PredXactListElementDataSize);
+ RWConflictPool->element = ShmemAlloc(requestSize);
+ if (RWConflictPool->element == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("not enough shared memory for elements of data structure"
+ " \"%s\" (%lu bytes requested)",
+ "RWConflictPool", (unsigned long) requestSize)));
+ /* Add all elements to available list, clean. */
+ memset(RWConflictPool->element, 0, requestSize);
+ for (i = 0; i < max_table_size; i++)
+ {
+ SHMQueueInsertBefore(&(RWConflictPool->availableList),
+ &(RWConflictPool->element[i].outLink));
+ }
+ }
+
+ /*
+ * Create or attach to the header for the list of finished serializable
+ * transactions.
+ */
+ FinishedSerializableTransactions = (SHM_QUEUE *)
+ ShmemInitStruct("FinishedSerializableTransactions",
+ sizeof(SHM_QUEUE),
+ &found);
+ if (!found)
+ SHMQueueInit(FinishedSerializableTransactions);
+
+ /*
+ * Initialize the SLRU storage for old committed serializable
+ * transactions.
+ */
+ OldSerXidInit();
+}
+
+/*
+ * Estimate shared-memory space used for predicate lock table
+ */
+Size
+PredicateLockShmemSize(void)
+{
+ Size size = 0;
+ long max_table_size;
+
+ /* predicate lock target hash table */
+ max_table_size = NPREDICATELOCKTARGETENTS();
+ size = add_size(size, hash_estimate_size(max_table_size,
+ sizeof(PREDICATELOCKTARGET)));
+
+ /* predicate lock hash table */
+ max_table_size *= 2;
+ size = add_size(size, hash_estimate_size(max_table_size,
+ sizeof(PREDICATELOCK)));
+
+ /*
+ * Since NPREDICATELOCKTARGETENTS is only an estimate, add 10% safety
+ * margin.
+ */
+ size = add_size(size, size / 10);
+
+ /* transaction list */
+ max_table_size = MaxBackends + max_prepared_xacts;
+ max_table_size *= 10;
+ size = add_size(size, PredXactListDataSize);
+ size = add_size(size, mul_size((Size) max_table_size,
+ PredXactListElementDataSize));
+
+ /* transaction xid table */
+ size = add_size(size, hash_estimate_size(max_table_size,
+ sizeof(SERIALIZABLEXID)));
+
+ /* Head for list of finished serializable transactions. */
+ size = add_size(size, sizeof(SHM_QUEUE));
+
+ /* Shared memory structures for SLRU tracking of old committed xids. */
+ size = add_size(size, sizeof(OldSerXidControl));
+ size = add_size(size, SimpleLruShmemSize(NUM_OLDSERXID_BUFFERS, 0));
+
+ return size;
+}
+
+
+/*
+ * Compute the hash code associated with a PREDICATELOCKTAG.
+ *
+ * Because we want to use just one set of partition locks for both the
+ * PREDICATELOCKTARGET and PREDICATELOCK hash tables, we have to make sure
+ * that PREDICATELOCKs fall into the same partition number as their
+ * associated PREDICATELOCKTARGETs. dynahash.c expects the partition number
+ * to be the low-order bits of the hash code, and therefore a
+ * PREDICATELOCKTAG's hash code must have the same low-order bits as the
+ * associated PREDICATELOCKTARGETTAG's hash code. We achieve this with this
+ * specialized hash function.
+ */
+static uint32
+predicatelock_hash(const void *key, Size keysize)
+{
+ const PREDICATELOCKTAG *predicatelocktag = (const PREDICATELOCKTAG *) key;
+ uint32 targethash;
+
+ Assert(keysize == sizeof(PREDICATELOCKTAG));
+
+ /* Look into the associated target object, and compute its hash code */
+ targethash = PredicateLockTargetTagHashCode(&predicatelocktag->myTarget->tag);
+
+ return PredicateLockHashCodeFromTargetHashCode(predicatelocktag, targethash);
+}
+
+
+/*
+ * GetPredicateLockStatusData
+ * Return a table containing the internal state of the predicate
+ * lock manager for use in pg_lock_status.
+ *
+ * Like GetLockStatusData, this function tries to hold the partition LWLocks
+ * for as short a time as possible by returning two arrays that simply
+ * contain the PREDICATELOCKTARGETTAG and SERIALIZABLEXACT for each lock
+ * table entry. Multiple copies of the same PREDICATELOCKTARGETTAG and
+ * SERIALIZABLEXACT will likely appear.
+ */
+PredicateLockData *
+GetPredicateLockStatusData(void)
+{
+ PredicateLockData *data;
+ int i;
+ int els,
+ el;
+ HASH_SEQ_STATUS seqstat;
+ PREDICATELOCK *predlock;
+
+ data = (PredicateLockData *) palloc(sizeof(PredicateLockData));
+
+ /*
+ * To ensure consistency, take simultaneous locks on all partition locks
+ * in ascending order, then SerializableXactHashLock.
+ */
+ for (i = 0; i < NUM_PREDICATELOCK_PARTITIONS; i++)
+ LWLockAcquire(FirstPredicateLockMgrLock + i, LW_SHARED);
+ LWLockAcquire(SerializableXactHashLock, LW_SHARED);
+
+ /* Get number of locks and allocate appropriately-sized arrays. */
+ els = hash_get_num_entries(PredicateLockHash);
+ data->nelements = els;
+ data->locktags = (PREDICATELOCKTARGETTAG *)
+ palloc(sizeof(PREDICATELOCKTARGETTAG) * els);
+ data->xacts = (SERIALIZABLEXACT *)
+ palloc(sizeof(SERIALIZABLEXACT) * els);
+
+
+ /* Scan through PredicateLockHash and copy contents */
+ hash_seq_init(&seqstat, PredicateLockHash);
+
+ el = 0;
+
+ while ((predlock = (PREDICATELOCK *) hash_seq_search(&seqstat)))
+ {
+ data->locktags[el] = predlock->tag.myTarget->tag;
+ data->xacts[el] = *predlock->tag.myXact;
+ el++;
+ }
+
+ Assert(el == els);
+
+ /* Release locks in reverse order */
+ LWLockRelease(SerializableXactHashLock);
+ for (i = NUM_PREDICATELOCK_PARTITIONS - 1; i >= 0; i--)
+ LWLockRelease(FirstPredicateLockMgrLock + i);
+
+ return data;
+}
+
+/*
+ * Free up shared memory structures by pushing the oldest sxact (the one at
+ * the front of the SummarizeOldestCommittedSxact queue) into summary form.
+ * Each call will free exactly one SERIALIZABLEXACT structure and may also
+ * free one or more of these structures: SERIALIZABLEXID, PREDICATELOCK,
+ * PREDICATELOCKTARGET, RWConflictData.
+ */
+static void
+SummarizeOldestCommittedSxact(void)
+{
+ SERIALIZABLEXACT *sxact;
+
+ LWLockAcquire(SerializableFinishedListLock, LW_EXCLUSIVE);
+
+#ifdef TEST_OLDSERXID
+ if (SHMQueueEmpty(FinishedSerializableTransactions))
+ {
+ LWLockRelease(SerializableFinishedListLock);
+ return;
+ }
+#else
+ Assert(!SHMQueueEmpty(FinishedSerializableTransactions));
+#endif
+
+ /*
+ * Grab the first sxact off the finished list -- this will be the earliest
+ * commit. Remove it from the list.
+ */
+ sxact = (SERIALIZABLEXACT *)
+ SHMQueueNext(FinishedSerializableTransactions,
+ FinishedSerializableTransactions,
+ offsetof(SERIALIZABLEXACT, finishedLink));
+ SHMQueueDelete(&(sxact->finishedLink));
+
+ /* Add to SLRU summary information. */
+ if (TransactionIdIsValid(sxact->topXid) && !SxactIsReadOnly(sxact))
+ OldSerXidAdd(sxact->topXid, SxactHasConflictOut(sxact)
+ ? sxact->SeqNo.earliestOutConflictCommit : InvalidSerCommitSeqNo);
+
+ /* Summarize and release the detail. */
+ ReleaseOneSerializableXact(sxact, false, true);
+
+ LWLockRelease(SerializableFinishedListLock);
+}
+
+/*
+ * GetSafeSnapshot
+ * Obtain and register a snapshot for a READ ONLY DEFERRABLE
+ * transaction. Ensures that the snapshot is "safe", i.e. a
+ * read-only transaction running on it can execute serializably
+ * without further checks. This requires waiting for concurrent
+ * transactions to complete, and retrying with a new snapshot if
+ * one of them could possibly create a conflict.
+ */
+static Snapshot
+GetSafeSnapshot(Snapshot origSnapshot)
+{
+ Snapshot snapshot;
+
+ Assert(XactReadOnly && XactDeferrable);
+
+ while (true)
+ {
+ /*
+ * RegisterSerializableTransactionInt is going to call
+ * GetSnapshotData, so we need to provide it the static snapshot our
+ * caller passed to us. It returns a copy of that snapshot and
+ * registers it on TopTransactionResourceOwner.
+ */
+ snapshot = RegisterSerializableTransactionInt(origSnapshot);
+
+ if (MySerializableXact == InvalidSerializableXact)
+ return snapshot; /* no concurrent r/w xacts; it's safe */
+
+ MySerializableXact->flags |= SXACT_FLAG_DEFERRABLE_WAITING;
+
+ /*
+ * Wait for concurrent transactions to finish. Stop early if one of
+ * them marked us as conflicted.
+ */
+ while (!(SHMQueueEmpty((SHM_QUEUE *)
+ &MySerializableXact->possibleUnsafeConflicts) ||
+ SxactIsROUnsafe(MySerializableXact)))
+ ProcWaitForSignal();
+
+ MySerializableXact->flags &= ~SXACT_FLAG_DEFERRABLE_WAITING;
+ if (!SxactIsROUnsafe(MySerializableXact))
+ break; /* success */
+
+ /* else, need to retry... */
+ ereport(DEBUG2,
+ (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+ errmsg("deferrable snapshot was unsafe; trying a new one")));
+ ReleasePredicateLocks(false);
+ UnregisterSnapshotFromOwner(snapshot,
+ TopTransactionResourceOwner);
+ }
+
+ /*
+ * Now we have a safe snapshot, so we don't need to do any further checks.
+ */
+ Assert(SxactIsROSafe(MySerializableXact));
+ ReleasePredicateLocks(false);
+
+ return snapshot;
+}
+
+/*
+ * Acquire and register a snapshot which can be used for this transaction..
+ * Make sure we have a SERIALIZABLEXACT reference in MySerializableXact.
+ * It should be current for this process and be contained in PredXact.
+ */
+Snapshot
+RegisterSerializableTransaction(Snapshot snapshot)
+{
+ Assert(IsolationIsSerializable());
+
+ /*
+ * A special optimization is available for SERIALIZABLE READ ONLY
+ * DEFERRABLE transactions -- we can wait for a suitable snapshot and
+ * thereby avoid all SSI overhead once it's running..
+ */
+ if (XactReadOnly && XactDeferrable)
+ return GetSafeSnapshot(snapshot);
+
+ return RegisterSerializableTransactionInt(snapshot);
+}
+
+static Snapshot
+RegisterSerializableTransactionInt(Snapshot snapshot)
+{
+ PGPROC *proc;
+ VirtualTransactionId vxid;
+ SERIALIZABLEXACT *sxact,
+ *othersxact;
+ HASHCTL hash_ctl;
+
+ /* We only do this for serializable transactions. Once. */
+ Assert(MySerializableXact == InvalidSerializableXact);
+
+ Assert(!RecoveryInProgress());
+
+ proc = MyProc;
+ Assert(proc != NULL);
+ GET_VXID_FROM_PGPROC(vxid, *proc);
+
+ /*
+ * First we get the sxact structure, which may involve looping and access
+ * to the "finished" list to free a structure for use.
+ */
+#ifdef TEST_OLDSERXID
+ SummarizeOldestCommittedSxact();
+#endif
+ LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
+ do
+ {
+ sxact = CreatePredXact();
+ /* If null, push out committed sxact to SLRU summary & retry. */
+ if (!sxact)
+ {
+ LWLockRelease(SerializableXactHashLock);
+ SummarizeOldestCommittedSxact();
+ LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
+ }
+ } while (!sxact);
+
+ /* Get and register a snapshot */
+ snapshot = GetSnapshotData(snapshot);
+ snapshot = RegisterSnapshotOnOwner(snapshot, TopTransactionResourceOwner);
+
+ /*
+ * If there are no serializable transactions which are not read-only, we
+ * can "opt out" of predicate locking and conflict checking for a
+ * read-only transaction.
+ *
+ * The reason this is safe is that a read-only transaction can only become
+ * part of a dangerous structure if it overlaps a writable transaction
+ * which in turn overlaps a writable transaction which committed before
+ * the read-only transaction started. A new writable transaction can
+ * overlap this one, but it can't meet the other condition of overlapping
+ * a transaction which committed before this one started.
+ */
+ if (XactReadOnly && PredXact->WritableSxactCount == 0)
+ {
+ ReleasePredXact(sxact);
+ LWLockRelease(SerializableXactHashLock);
+ return snapshot;
+ }
+
+ /* Maintain serializable global xmin info. */
+ if (!TransactionIdIsValid(PredXact->SxactGlobalXmin))
+ {
+ Assert(PredXact->SxactGlobalXminCount == 0);
+ PredXact->SxactGlobalXmin = snapshot->xmin;
+ PredXact->SxactGlobalXminCount = 1;
+ OldSerXidSetActiveSerXmin(snapshot->xmin);
+ }
+ else if (TransactionIdEquals(snapshot->xmin, PredXact->SxactGlobalXmin))
+ {
+ Assert(PredXact->SxactGlobalXminCount > 0);
+ PredXact->SxactGlobalXminCount++;
+ }
+ else
+ {
+ Assert(TransactionIdFollows(snapshot->xmin, PredXact->SxactGlobalXmin));
+ }
+
+ /* Initialize the structure. */
+ sxact->vxid = vxid;
+ sxact->SeqNo.lastCommitBeforeSnapshot = PredXact->LastSxactCommitSeqNo;
+ sxact->commitSeqNo = InvalidSerCommitSeqNo;
+ SHMQueueInit(&(sxact->outConflicts));
+ SHMQueueInit(&(sxact->inConflicts));
+ SHMQueueInit(&(sxact->possibleUnsafeConflicts));
+ sxact->topXid = GetTopTransactionIdIfAny();
+ sxact->finishedBefore = InvalidTransactionId;
+ sxact->xmin = snapshot->xmin;
+ sxact->pid = MyProcPid;
+ SHMQueueInit(&(sxact->predicateLocks));
+ SHMQueueElemInit(&(sxact->finishedLink));
+ sxact->flags = 0;
+ if (XactReadOnly)
+ {
+ sxact->flags |= SXACT_FLAG_READ_ONLY;
+
+ /*
+ * Register all concurrent r/w transactions as possible conflicts; if
+ * all of them commit without any outgoing conflicts to earlier
+ * transactions then this snapshot can be deemed safe (and we can run
+ * without tracking predicate locks).
+ */
+ for (othersxact = FirstPredXact();
+ othersxact != NULL;
+ othersxact = NextPredXact(othersxact))
+ {
+ if (!SxactIsOnFinishedList(othersxact) &&
+ !SxactIsReadOnly(othersxact))
+ {
+ SetPossibleUnsafeConflict(sxact, othersxact);
+ }
+ }
+ }
+ else
+ {
+ ++(PredXact->WritableSxactCount);
+ Assert(PredXact->WritableSxactCount <=
+ (MaxBackends + max_prepared_xacts));
+ }
+
+ MySerializableXact = sxact;
+
+ LWLockRelease(SerializableXactHashLock);
+
+ /* Initialize the backend-local hash table of parent locks */
+ Assert(LocalPredicateLockHash == NULL);
+ MemSet(&hash_ctl, 0, sizeof(hash_ctl));
+ hash_ctl.keysize = sizeof(PREDICATELOCKTARGETTAG);
+ hash_ctl.entrysize = sizeof(LOCALPREDICATELOCK);
+ hash_ctl.hash = tag_hash;
+ LocalPredicateLockHash = hash_create("Local predicate lock",
+ max_predicate_locks_per_xact,
+ &hash_ctl,
+ HASH_ELEM | HASH_FUNCTION);
+
+ return snapshot;
+}
+
+/*
+ * Register the top level XID in SerializableXidHash.
+ * Also store it for easy reference in MySerializableXact.
+ */
+void
+RegisterPredicateLockingXid(const TransactionId xid)
+{
+ SERIALIZABLEXIDTAG sxidtag;
+ SERIALIZABLEXID *sxid;
+ bool found;
+
+ /*
+ * If we're not tracking predicate lock data for this transaction, we
+ * should ignore the request and return quickly.
+ */
+ if (MySerializableXact == InvalidSerializableXact)
+ return;
+
+ /* This should only be done once per transaction. */
+ Assert(MySerializableXact->topXid == InvalidTransactionId);
+
+ /* We should have a valid XID and be at the top level. */
+ Assert(TransactionIdIsValid(xid));
+
+ MySerializableXact->topXid = xid;
+
+ sxidtag.xid = xid;
+ LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
+ sxid = (SERIALIZABLEXID *) hash_search(SerializableXidHash,
+ &sxidtag,
+ HASH_ENTER, &found);
+ if (!sxid)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_predicate_locks_per_transaction.")));
+
+ Assert(!found);
+
+ /* Initialize the structure. */
+ sxid->myXact = (SERIALIZABLEXACT *) MySerializableXact;
+ LWLockRelease(SerializableXactHashLock);
+}
+
+
+/*
+ * Check whether there are any predicate locks held by any transaction
+ * for the page at the given block number.
+ *
+ * Note that the transaction may be completed but not yet subject to
+ * cleanup due to overlapping serializable transactions. This must
+ * return valid information regardless of transaction isolation level.
+ *
+ * Also note that this doesn't check for a conflicting relation lock,
+ * just a lock specifically on the given page.
+ *
+ * One use is to support proper behavior during GiST index vacuum.
+ */
+bool
+PageIsPredicateLocked(const Relation relation, const BlockNumber blkno)
+{
+ PREDICATELOCKTARGETTAG targettag;
+ uint32 targettaghash;
+ LWLockId partitionLock;
+ PREDICATELOCKTARGET *target;
+
+ SET_PREDICATELOCKTARGETTAG_PAGE(targettag,
+ relation->rd_node.dbNode,
+ relation->rd_id,
+ blkno);
+
+ targettaghash = PredicateLockTargetTagHashCode(&targettag);
+ partitionLock = PredicateLockHashPartitionLock(targettaghash);
+ LWLockAcquire(partitionLock, LW_SHARED);
+ target = (PREDICATELOCKTARGET *)
+ hash_search_with_hash_value(PredicateLockTargetHash,
+ &targettag, targettaghash,
+ HASH_FIND, NULL);
+ LWLockRelease(partitionLock);
+
+ return (target != NULL);
+}
+
+
+/*
+ * Check whether a particular lock is held by this transaction.
+ *
+ * Important note: this function may return false even if the lock is
+ * being held, because it uses the local lock table which is not
+ * updated if another transaction modifies our lock list (e.g. to
+ * split an index page). However, it will never return true if the
+ * lock is not held. We only use this function in circumstances where
+ * such false negatives are acceptable.
+ */
+static bool
+PredicateLockExists(const PREDICATELOCKTARGETTAG *targettag)
+{
+ LOCALPREDICATELOCK *lock;
+
+ /* check local hash table */
+ lock = (LOCALPREDICATELOCK *) hash_search(LocalPredicateLockHash,
+ targettag,
+ HASH_FIND, NULL);
+
+ if (!lock)
+ return false;
+
+ /*
+ * Found entry in the table, but still need to check whether it's actually
+ * held -- it could just be a parent of some held lock.
+ */
+ return lock->held;
+}
+
+/*
+ * Return the parent lock tag in the lock hierarchy: the next coarser
+ * lock that covers the provided tag.
+ *
+ * Returns true and sets *parent to the parent tag if one exists,
+ * returns false if none exists.
+ */
+static bool
+GetParentPredicateLockTag(const PREDICATELOCKTARGETTAG *tag,
+ PREDICATELOCKTARGETTAG *parent)
+{
+ switch (GET_PREDICATELOCKTARGETTAG_TYPE(*tag))
+ {
+ case PREDLOCKTAG_RELATION:
+ /* relation locks have no parent lock */
+ return false;
+
+ case PREDLOCKTAG_PAGE:
+ /* parent lock is relation lock */
+ SET_PREDICATELOCKTARGETTAG_RELATION(*parent,
+ GET_PREDICATELOCKTARGETTAG_DB(*tag),
+ GET_PREDICATELOCKTARGETTAG_RELATION(*tag));
+
+ return true;
+
+ case PREDLOCKTAG_TUPLE:
+ /* parent lock is page lock */
+ SET_PREDICATELOCKTARGETTAG_PAGE(*parent,
+ GET_PREDICATELOCKTARGETTAG_DB(*tag),
+ GET_PREDICATELOCKTARGETTAG_RELATION(*tag),
+ GET_PREDICATELOCKTARGETTAG_PAGE(*tag));
+ return true;
+ }
+
+ /* not reachable */
+ Assert(false);
+ return false;
+}
+
+/*
+ * Check whether the lock we are considering is already covered by a
+ * coarser lock for our transaction.
+ */
+static bool
+CoarserLockCovers(const PREDICATELOCKTARGETTAG *newtargettag)
+{
+ PREDICATELOCKTARGETTAG targettag,
+ parenttag;
+
+ targettag = *newtargettag;
+
+ /* check parents iteratively until no more */
+ while (GetParentPredicateLockTag(&targettag, &parenttag))
+ {
+ targettag = parenttag;
+ if (PredicateLockExists(&targettag))
+ return true;
+ }
+
+ /* no more parents to check; lock is not covered */
+ return false;
+}
+
+/*
+ * Check whether both the list of related predicate locks and the pointer to
+ * a prior version of the row (if this is a tuple lock target) are empty for
+ * a predicate lock target, and remove the target if they are.
+ */
+static void
+RemoveTargetIfNoLongerUsed(PREDICATELOCKTARGET *target, uint32 targettaghash)
+{
+ PREDICATELOCKTARGET *rmtarget;
+ PREDICATELOCKTARGET *next;
+
+ Assert(LWLockHeldByMe(SerializablePredicateLockListLock));
+
+ /* Can't remove it until no locks at this target. */
+ if (!SHMQueueEmpty(&target->predicateLocks))
+ return;
+
+ /* Can't remove it if there are locks for a prior row version. */
+ LWLockAcquire(PredicateLockNextRowLinkLock, LW_EXCLUSIVE);
+ if (target->priorVersionOfRow != NULL)
+ {
+ LWLockRelease(PredicateLockNextRowLinkLock);
+ return;
+ }
+
+ /*
+ * We are going to release this target, This requires that we let the
+ * next version of the row (if any) know that it's previous version is
+ * done.
+ *
+ * It might be that the link was all that was keeping the other target
+ * from cleanup, but we can't clean that up here -- LW locking is all
+ * wrong for that. We'll pass the HTAB in the general cleanup function to
+ * get rid of such "dead" targets.
+ */
+ next = target->nextVersionOfRow;
+ if (next != NULL)
+ {
+ next->priorVersionOfRow = NULL;
+ if (SHMQueueEmpty(&next->predicateLocks))
+ PredXact->NeedTargetLinkCleanup = true;
+ }
+ LWLockRelease(PredicateLockNextRowLinkLock);
+
+ /* Actually remove the target. */
+ rmtarget = hash_search_with_hash_value(PredicateLockTargetHash,
+ &target->tag,
+ targettaghash,
+ HASH_REMOVE, NULL);
+ Assert(rmtarget == target);
+}
+
+/*
+ * Delete child target locks owned by this process.
+ * This implementation is assuming that the usage of each target tag field
+ * is uniform. No need to make this hard if we don't have to.
+ *
+ * We aren't acquiring lightweight locks for the predicate lock or lock
+ * target structures associated with this transaction unless we're going
+ * to modify them, because no other process is permitted to modify our
+ * locks.
+ */
+static void
+DeleteChildTargetLocks(const PREDICATELOCKTARGETTAG *newtargettag)
+{
+ SERIALIZABLEXACT *sxact;
+ PREDICATELOCK *predlock;
+
+ LWLockAcquire(SerializablePredicateLockListLock, LW_SHARED);
+ sxact = (SERIALIZABLEXACT *) MySerializableXact;
+ predlock = (PREDICATELOCK *)
+ SHMQueueNext(&(sxact->predicateLocks),
+ &(sxact->predicateLocks),
+ offsetof(PREDICATELOCK, xactLink));
+ while (predlock)
+ {
+ SHM_QUEUE *predlocksxactlink;
+ PREDICATELOCK *nextpredlock;
+ PREDICATELOCKTAG oldlocktag;
+ PREDICATELOCKTARGET *oldtarget;
+ PREDICATELOCKTARGETTAG oldtargettag;
+
+ predlocksxactlink = &(predlock->xactLink);
+ nextpredlock = (PREDICATELOCK *)
+ SHMQueueNext(&(sxact->predicateLocks),
+ predlocksxactlink,
+ offsetof(PREDICATELOCK, xactLink));
+
+ oldlocktag = predlock->tag;
+ Assert(oldlocktag.myXact == sxact);
+ oldtarget = oldlocktag.myTarget;
+ oldtargettag = oldtarget->tag;
+
+ if (TargetTagIsCoveredBy(oldtargettag, *newtargettag))
+ {
+ uint32 oldtargettaghash;
+ LWLockId partitionLock;
+ PREDICATELOCK *rmpredlock;
+
+ oldtargettaghash = PredicateLockTargetTagHashCode(&oldtargettag);
+ partitionLock = PredicateLockHashPartitionLock(oldtargettaghash);
+
+ LWLockAcquire(partitionLock, LW_EXCLUSIVE);
+
+ SHMQueueDelete(predlocksxactlink);
+ SHMQueueDelete(&(predlock->targetLink));
+ rmpredlock = hash_search_with_hash_value
+ (PredicateLockHash,
+ &oldlocktag,
+ PredicateLockHashCodeFromTargetHashCode(&oldlocktag,
+ oldtargettaghash),
+ HASH_REMOVE, NULL);
+ Assert(rmpredlock == predlock);
+
+ RemoveTargetIfNoLongerUsed(oldtarget, oldtargettaghash);
+
+ LWLockRelease(partitionLock);
+
+ DecrementParentLocks(&oldtargettag);
+ }
+
+ predlock = nextpredlock;
+ }
+ LWLockRelease(SerializablePredicateLockListLock);
+}
+
+/*
+ * Returns the promotion threshold for a given predicate lock
+ * target. This is the number of descendant locks required to promote
+ * to the specified tag. Note that the threshold includes non-direct
+ * descendants, e.g. both tuples and pages for a relation lock.
+ *
+ * TODO SSI: We should do something more intelligent about what the
+ * thresholds are, either making it proportional to the number of
+ * tuples in a page & pages in a relation, or at least making it a
+ * GUC. Currently the threshold is 3 for a page lock, and
+ * max_predicate_locks_per_transaction/2 for a relation lock, chosen
+ * entirely arbitrarily (and without benchmarking).
+ */
+static int
+PredicateLockPromotionThreshold(const PREDICATELOCKTARGETTAG *tag)
+{
+ switch (GET_PREDICATELOCKTARGETTAG_TYPE(*tag))
+ {
+ case PREDLOCKTAG_RELATION:
+ return max_predicate_locks_per_xact / 2;
+
+ case PREDLOCKTAG_PAGE:
+ return 3;
+
+ case PREDLOCKTAG_TUPLE:
+
+ /*
+ * not reachable: nothing is finer-granularity than a tuple, so we
+ * should never try to promote to it.
+ */
+ Assert(false);
+ return 0;
+ }
+
+ /* not reachable */
+ Assert(false);
+ return 0;
+}
+
+/*
+ * For all ancestors of a newly-acquired predicate lock, increment
+ * their child count in the parent hash table. If any of them have
+ * more descendants than their promotion threshold, acquire the
+ * coarsest such lock.
+ *
+ * Returns true if a parent lock was acquired and false otherwise.
+ */
+static bool
+CheckAndPromotePredicateLockRequest(const PREDICATELOCKTARGETTAG *reqtag)
+{
+ PREDICATELOCKTARGETTAG targettag,
+ nexttag,
+ promotiontag;
+ LOCALPREDICATELOCK *parentlock;
+ bool found,
+ promote;
+
+ promote = false;
+
+ targettag = *reqtag;
+
+ /* check parents iteratively */
+ while (GetParentPredicateLockTag(&targettag, &nexttag))
+ {
+ targettag = nexttag;
+ parentlock = (LOCALPREDICATELOCK *) hash_search(LocalPredicateLockHash,
+ &targettag,
+ HASH_ENTER,
+ &found);
+ if (!found)
+ {
+ parentlock->held = false;
+ parentlock->childLocks = 1;
+ }
+ else
+ parentlock->childLocks++;
+
+ if (parentlock->childLocks >=
+ PredicateLockPromotionThreshold(&targettag))
+ {
+ /*
+ * We should promote to this parent lock. Continue to check its
+ * ancestors, however, both to get their child counts right and to
+ * check whether we should just go ahead and promote to one of
+ * them.
+ */
+ promotiontag = targettag;
+ promote = true;
+ }
+ }
+
+ if (promote)
+ {
+ /* acquire coarsest ancestor eligible for promotion */
+ PredicateLockAcquire(&promotiontag);
+ return true;
+ }
+ else
+ return false;
+}
+
+/*
+ * When releasing a lock, decrement the child count on all ancestor
+ * locks.
+ *
+ * This is called only when releasing a lock via
+ * DeleteChildTargetLocks (i.e. when a lock becomes redundant because
+ * we've acquired its parent, possibly due to promotion) or when a new
+ * MVCC write lock makes the predicate lock unnecessary. There's no
+ * point in calling it when locks are released at transaction end, as
+ * this information is no longer needed.
+ */
+static void
+DecrementParentLocks(const PREDICATELOCKTARGETTAG *targettag)
+{
+ PREDICATELOCKTARGETTAG parenttag,
+ nexttag;
+
+ parenttag = *targettag;
+
+ while (GetParentPredicateLockTag(&parenttag, &nexttag))
+ {
+ uint32 targettaghash;
+ LOCALPREDICATELOCK *parentlock,
+ *rmlock;
+
+ parenttag = nexttag;
+ targettaghash = PredicateLockTargetTagHashCode(&parenttag);
+ parentlock = (LOCALPREDICATELOCK *)
+ hash_search_with_hash_value(LocalPredicateLockHash,
+ &parenttag, targettaghash,
+ HASH_FIND, NULL);
+
+ /*
+ * There's a small chance the parent lock doesn't exist in the lock
+ * table. This can happen if we prematurely removed it because an
+ * index split caused the child refcount to be off.
+ */
+ if (parentlock == NULL)
+ continue;
+
+ parentlock->childLocks--;
+
+ /*
+ * Under similar circumstances the parent lock's refcount might be
+ * zero. This only happens if we're holding that lock (otherwise we
+ * would have removed the entry).
+ */
+ if (parentlock->childLocks < 0)
+ {
+ Assert(parentlock->held);
+ parentlock->childLocks = 0;
+ }
+
+ if ((parentlock->childLocks == 0) && (!parentlock->held))
+ {
+ rmlock = (LOCALPREDICATELOCK *)
+ hash_search_with_hash_value(LocalPredicateLockHash,
+ &parenttag, targettaghash,
+ HASH_REMOVE, NULL);
+ Assert(rmlock == parentlock);
+ }
+ }
+}
+
+/*
+ * Indicate that a predicate lock on the given target is held by the
+ * specified transaction. Has no effect if the lock is already held.
+ *
+ * This updates the lock table and the sxact's lock list, and creates
+ * the lock target if necessary, but does *not* do anything related to
+ * granularity promotion or the local lock table. See
+ * PredicateLockAcquire for that.
+ */
+static void
+CreatePredicateLock(const PREDICATELOCKTARGETTAG *targettag,
+ uint32 targettaghash,
+ SERIALIZABLEXACT *sxact)
+{
+ PREDICATELOCKTARGET *target;
+ PREDICATELOCKTAG locktag;
+ PREDICATELOCK *lock;
+ LWLockId partitionLock;
+ bool found;
+
+ partitionLock = PredicateLockHashPartitionLock(targettaghash);
+
+ LWLockAcquire(SerializablePredicateLockListLock, LW_SHARED);
+ LWLockAcquire(partitionLock, LW_EXCLUSIVE);
+
+ /* Make sure that the target is represented. */
+ target = (PREDICATELOCKTARGET *)
+ hash_search_with_hash_value(PredicateLockTargetHash,
+ targettag, targettaghash,
+ HASH_ENTER, &found);
+ if (!target)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_predicate_locks_per_transaction.")));
+ if (!found)
+ {
+ SHMQueueInit(&(target->predicateLocks));
+ target->priorVersionOfRow = NULL;
+ target->nextVersionOfRow = NULL;
+ }
+
+ /* We've got the sxact and target, make sure they're joined. */
+ locktag.myTarget = target;
+ locktag.myXact = sxact;
+ lock = (PREDICATELOCK *)
+ hash_search_with_hash_value(PredicateLockHash, &locktag,
+ PredicateLockHashCodeFromTargetHashCode(&locktag, targettaghash),
+ HASH_ENTER, &found);
+ if (!lock)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_predicate_locks_per_transaction.")));
+
+ if (!found)
+ {
+ SHMQueueInsertBefore(&(target->predicateLocks), &(lock->targetLink));
+ SHMQueueInsertBefore(&(sxact->predicateLocks),
+ &(lock->xactLink));
+ lock->commitSeqNo = 0;
+ }
+
+ LWLockRelease(partitionLock);
+ LWLockRelease(SerializablePredicateLockListLock);
+}
+
+/*
+ * Acquire a predicate lock on the specified target for the current
+ * connection if not already held. This updates the local lock table
+ * and uses it to implement granularity promotion. It will consolidate
+ * multiple locks into a coarser lock if warranted, and will release
+ * any finer-grained locks covered by the new one.
+ */
+static void
+PredicateLockAcquire(const PREDICATELOCKTARGETTAG *targettag)
+{
+ uint32 targettaghash;
+ bool found;
+ LOCALPREDICATELOCK *locallock;
+
+ /* Do we have the lock already, or a covering lock? */
+ if (PredicateLockExists(targettag))
+ return;
+
+ if (CoarserLockCovers(targettag))
+ return;
+
+ /* the same hash and LW lock apply to the lock target and the local lock. */
+ targettaghash = PredicateLockTargetTagHashCode(targettag);
+
+ /* Acquire lock in local table */
+ locallock = (LOCALPREDICATELOCK *)
+ hash_search_with_hash_value(LocalPredicateLockHash,
+ targettag, targettaghash,
+ HASH_ENTER, &found);
+ /* We should not hold the lock (but its entry might still exist) */
+ Assert(!found || !locallock->held);
+ locallock->held = true;
+ if (!found)
+ locallock->childLocks = 0;
+
+ /* Actually create the lock */
+ CreatePredicateLock(targettag, targettaghash,
+ (SERIALIZABLEXACT *) MySerializableXact);
+
+ /*
+ * Lock has been acquired. Check whether it should be promoted to a
+ * coarser granularity, or whether there are finer-granularity locks to
+ * clean up.
+ */
+ if (CheckAndPromotePredicateLockRequest(targettag))
+ {
+ /*
+ * Lock request was promoted to a coarser-granularity lock, and that
+ * lock was acquired. It will delete this lock and any of its
+ * children, so we're done.
+ */
+ }
+ else
+ {
+ /* Clean up any finer-granularity locks */
+ if (GET_PREDICATELOCKTARGETTAG_TYPE(*targettag) != PREDLOCKTAG_TUPLE)
+ DeleteChildTargetLocks(targettag);
+ }
+}
+
+
+/*
+ * PredicateLockRelation
+ *
+ * Gets a predicate lock at the relation level.
+ * Skip if not in full serializable transaction isolation level.
+ * Skip if this is a temporary table.
+ * Clear any finer-grained predicate locks this session has on the relation.
+ */
+void
+PredicateLockRelation(const Relation relation)
+{
+ PREDICATELOCKTARGETTAG tag;
+
+ if (SkipSerialization(relation))
+ return;
+
+ SET_PREDICATELOCKTARGETTAG_RELATION(tag,
+ relation->rd_node.dbNode,
+ relation->rd_id);
+ PredicateLockAcquire(&tag);
+}
+
+/*
+ * PredicateLockPage
+ *
+ * Gets a predicate lock at the page level.
+ * Skip if not in full serializable transaction isolation level.
+ * Skip if this is a temporary table.
+ * Skip if a coarser predicate lock already covers this page.
+ * Clear any finer-grained predicate locks this session has on the relation.
+ */
+void
+PredicateLockPage(const Relation relation, const BlockNumber blkno)
+{
+ PREDICATELOCKTARGETTAG tag;
+
+ if (SkipSerialization(relation))
+ return;
+
+ SET_PREDICATELOCKTARGETTAG_PAGE(tag,
+ relation->rd_node.dbNode,
+ relation->rd_id,
+ blkno);
+ PredicateLockAcquire(&tag);
+}
+
+/*
+ * PredicateLockTuple
+ *
+ * Gets a predicate lock at the tuple level.
+ * Skip if not in full serializable transaction isolation level.
+ * Skip if this is a temporary table.
+ */
+void
+PredicateLockTuple(const Relation relation, const HeapTuple tuple)
+{
+ PREDICATELOCKTARGETTAG tag;
+ ItemPointer tid;
+
+ if (SkipSerialization(relation))
+ return;
+
+ /*
+ * If it's a heap tuple, return if this xact wrote it.
+ */
+ if (relation->rd_index == NULL)
+ {
+ TransactionId myxid = GetTopTransactionIdIfAny();
+
+ if (TransactionIdIsValid(myxid))
+ {
+ TransactionId xid = HeapTupleHeaderGetXmin(tuple->t_data);
+
+ if (TransactionIdFollowsOrEquals(xid, TransactionXmin))
+ {
+ xid = SubTransGetTopmostTransaction(xid);
+ if (TransactionIdEquals(xid, myxid))
+ {
+ /* We wrote it; we already have a write lock. */
+ return;
+ }
+ }
+ }
+ }
+
+ /*
+ * Do quick-but-not-definitive test for a relation lock first. This will
+ * never cause a return when the relation is *not* locked, but will
+ * occasionally let the check continue when there really *is* a relation
+ * level lock.
+ */
+ SET_PREDICATELOCKTARGETTAG_RELATION(tag,
+ relation->rd_node.dbNode,
+ relation->rd_id);
+ if (PredicateLockExists(&tag))
+ return;
+
+ tid = &(tuple->t_self);
+ SET_PREDICATELOCKTARGETTAG_TUPLE(tag,
+ relation->rd_node.dbNode,
+ relation->rd_id,
+ ItemPointerGetBlockNumber(tid),
+ ItemPointerGetOffsetNumber(tid));
+ PredicateLockAcquire(&tag);
+}
+
+/*
+ * If the old tuple has any predicate locks, create a lock target for the
+ * new tuple and point them at each other. Conflict detection needs to
+ * look for locks against prior versions of the row.
+ */
+void
+PredicateLockTupleRowVersionLink(const Relation relation,
+ const HeapTuple oldTuple,
+ const HeapTuple newTuple)
+{
+ PREDICATELOCKTARGETTAG oldtargettag;
+ PREDICATELOCKTARGETTAG newtargettag;
+ PREDICATELOCKTARGET *oldtarget;
+ PREDICATELOCKTARGET *newtarget;
+ PREDICATELOCKTARGET *next;
+ uint32 oldtargettaghash;
+ LWLockId oldpartitionLock;
+ uint32 newtargettaghash;
+ LWLockId newpartitionLock;
+ bool found;
+
+ SET_PREDICATELOCKTARGETTAG_TUPLE(oldtargettag,
+ relation->rd_node.dbNode,
+ relation->rd_id,
+ ItemPointerGetBlockNumber(&(oldTuple->t_self)),
+ ItemPointerGetOffsetNumber(&(oldTuple->t_self)));
+ oldtargettaghash = PredicateLockTargetTagHashCode(&oldtargettag);
+ oldpartitionLock = PredicateLockHashPartitionLock(oldtargettaghash);
+
+ SET_PREDICATELOCKTARGETTAG_TUPLE(newtargettag,
+ relation->rd_node.dbNode,
+ relation->rd_id,
+ ItemPointerGetBlockNumber(&(newTuple->t_self)),
+ ItemPointerGetOffsetNumber(&(newTuple->t_self)));
+ newtargettaghash = PredicateLockTargetTagHashCode(&newtargettag);
+ newpartitionLock = PredicateLockHashPartitionLock(newtargettaghash);
+
+ /* Lock lower numbered partition first. */
+ if (oldpartitionLock < newpartitionLock)
+ {
+ LWLockAcquire(oldpartitionLock, LW_SHARED);
+ LWLockAcquire(newpartitionLock, LW_EXCLUSIVE);
+ }
+ else if (newpartitionLock < oldpartitionLock)
+ {
+ LWLockAcquire(newpartitionLock, LW_EXCLUSIVE);
+ LWLockAcquire(oldpartitionLock, LW_SHARED);
+ }
+ else
+ LWLockAcquire(newpartitionLock, LW_EXCLUSIVE);
+
+ oldtarget = (PREDICATELOCKTARGET *)
+ hash_search_with_hash_value(PredicateLockTargetHash,
+ &oldtargettag, oldtargettaghash,
+ HASH_FIND, NULL);
+
+ /* Only need to link if there is an old target already. */
+ if (oldtarget)
+ {
+ LWLockAcquire(PredicateLockNextRowLinkLock, LW_EXCLUSIVE);
+
+ /* Guard against stale pointers from rollback. */
+ next = oldtarget->nextVersionOfRow;
+ if (next != NULL)
+ {
+ next->priorVersionOfRow = NULL;
+ oldtarget->nextVersionOfRow = NULL;
+ }
+
+ /* Find or create the new target, and link old and new. */
+ newtarget = (PREDICATELOCKTARGET *)
+ hash_search_with_hash_value(PredicateLockTargetHash,
+ &newtargettag, newtargettaghash,
+ HASH_ENTER, &found);
+ if (!newtarget)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_predicate_locks_per_transaction.")));
+ if (!found)
+ {
+ SHMQueueInit(&(newtarget->predicateLocks));
+ newtarget->nextVersionOfRow = NULL;
+ }
+ else
+ Assert(newtarget->priorVersionOfRow == NULL);
+
+ newtarget->priorVersionOfRow = oldtarget;
+ oldtarget->nextVersionOfRow = newtarget;
+
+ LWLockRelease(PredicateLockNextRowLinkLock);
+ }
+
+ /* Release lower number partition last. */
+ if (oldpartitionLock < newpartitionLock)
+ {
+ LWLockRelease(newpartitionLock);
+ LWLockRelease(oldpartitionLock);
+ }
+ else if (newpartitionLock < oldpartitionLock)
+ {
+ LWLockRelease(oldpartitionLock);
+ LWLockRelease(newpartitionLock);
+ }
+ else
+ LWLockRelease(newpartitionLock);
+}
+
+
+/*
+ * DeleteLockTarget
+ *
+ * Remove a predicate lock target along with any locks held for it.
+ *
+ * Caller must hold SerializablePredicateLockListLock and the
+ * appropriate hash partition lock for the target.
+ */
+static void
+DeleteLockTarget(PREDICATELOCKTARGET *target, uint32 targettaghash)
+{
+ PREDICATELOCK *predlock;
+ SHM_QUEUE *predlocktargetlink;
+ PREDICATELOCK *nextpredlock;
+ bool found;
+
+ Assert(LWLockHeldByMe(SerializablePredicateLockListLock));
+ Assert(LWLockHeldByMe(PredicateLockHashPartitionLock(targettaghash)));
+
+ predlock = (PREDICATELOCK *)
+ SHMQueueNext(&(target->predicateLocks),
+ &(target->predicateLocks),
+ offsetof(PREDICATELOCK, targetLink));
+ LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
+ while (predlock)
+ {
+ predlocktargetlink = &(predlock->targetLink);
+ nextpredlock = (PREDICATELOCK *)
+ SHMQueueNext(&(target->predicateLocks),
+ predlocktargetlink,
+ offsetof(PREDICATELOCK, targetLink));
+
+ SHMQueueDelete(&(predlock->xactLink));
+ SHMQueueDelete(&(predlock->targetLink));
+
+ hash_search_with_hash_value
+ (PredicateLockHash,
+ &predlock->tag,
+ PredicateLockHashCodeFromTargetHashCode(&predlock->tag,
+ targettaghash),
+ HASH_REMOVE, &found);
+ Assert(found);
+
+ predlock = nextpredlock;
+ }
+ LWLockRelease(SerializableXactHashLock);
+
+ /* Remove the target itself, if possible. */
+ RemoveTargetIfNoLongerUsed(target, targettaghash);
+}
+
+
+/*
+ * TransferPredicateLocksToNewTarget
+ *
+ * Move or copy all the predicate locks for a lock target, for use by
+ * index page splits/combines and other things that create or replace
+ * lock targets. If 'removeOld' is true, the old locks and the target
+ * will be removed.
+ *
+ * Returns true on success, or false if we ran out of shared memory to
+ * allocate the new target or locks. Guaranteed to always succeed if
+ * removeOld is set (by using the reserved entry in
+ * PredicateLockTargetHash for scratch space).
+ *
+ * Caller must hold SerializablePredicateLockListLock.
+ */
+static bool
+TransferPredicateLocksToNewTarget(const PREDICATELOCKTARGETTAG oldtargettag,
+ const PREDICATELOCKTARGETTAG newtargettag,
+ bool removeOld)
+{
+ uint32 oldtargettaghash;
+ LWLockId oldpartitionLock;
+ PREDICATELOCKTARGET *oldtarget;
+ uint32 newtargettaghash;
+ LWLockId newpartitionLock;
+ bool found;
+ bool outOfShmem = false;
+ uint32 reservedtargettaghash;
+ LWLockId reservedpartitionLock;
+
+
+ Assert(LWLockHeldByMe(SerializablePredicateLockListLock));
+
+ oldtargettaghash = PredicateLockTargetTagHashCode(&oldtargettag);
+ newtargettaghash = PredicateLockTargetTagHashCode(&newtargettag);
+ oldpartitionLock = PredicateLockHashPartitionLock(oldtargettaghash);
+ newpartitionLock = PredicateLockHashPartitionLock(newtargettaghash);
+
+ reservedtargettaghash = 0; /* Quiet compiler warnings. */
+ reservedpartitionLock = 0; /* Quiet compiler warnings. */
+
+ if (removeOld)
+ {
+ /*
+ * Remove the reserved entry to give us scratch space, so we know
+ * we'll be able to create the new lock target.
+ */
+ reservedtargettaghash = PredicateLockTargetTagHashCode(&ReservedTargetTag);
+ reservedpartitionLock = PredicateLockHashPartitionLock(reservedtargettaghash);
+ LWLockAcquire(reservedpartitionLock, LW_EXCLUSIVE);
+ hash_search_with_hash_value(PredicateLockTargetHash,
+ &ReservedTargetTag,
+ reservedtargettaghash,
+ HASH_REMOVE, &found);
+ Assert(found);
+ LWLockRelease(reservedpartitionLock);
+ }
+
+ /*
+ * We must get the partition locks in ascending sequence to avoid
+ * deadlocks. If old and new partitions are the same, we must request the
+ * lock only once.
+ */
+ if (oldpartitionLock < newpartitionLock)
+ {
+ LWLockAcquire(oldpartitionLock,
+ (removeOld ? LW_EXCLUSIVE : LW_SHARED));
+ LWLockAcquire(newpartitionLock, LW_EXCLUSIVE);
+ }
+ else if (oldpartitionLock > newpartitionLock)
+ {
+ LWLockAcquire(newpartitionLock, LW_EXCLUSIVE);
+ LWLockAcquire(oldpartitionLock,
+ (removeOld ? LW_EXCLUSIVE : LW_SHARED));
+ }
+ else
+ LWLockAcquire(newpartitionLock, LW_EXCLUSIVE);
+
+ /*
+ * Look for the old target. If not found, that's OK; no predicate locks
+ * are affected, so we can just clean up and return. If it does exist,
+ * walk its list of predicate locks and move or copy them to the new
+ * target.
+ */
+ oldtarget = hash_search_with_hash_value(PredicateLockTargetHash,
+ &oldtargettag,
+ oldtargettaghash,
+ HASH_FIND, NULL);
+
+ if (oldtarget)
+ {
+ PREDICATELOCKTARGET *newtarget;
+ PREDICATELOCK *oldpredlock;
+ PREDICATELOCKTAG newpredlocktag;
+
+ newtarget = hash_search_with_hash_value(PredicateLockTargetHash,
+ &newtargettag,
+ newtargettaghash,
+ HASH_ENTER_NULL, &found);
+
+ if (!newtarget)
+ {
+ /* Failed to allocate due to insufficient shmem */
+ outOfShmem = true;
+ goto exit;
+ }
+
+ /* If we created a new entry, initialize it */
+ if (!found)
+ {
+ SHMQueueInit(&(newtarget->predicateLocks));
+ newpredlocktag.myTarget = newtarget;
+ }
+
+ oldpredlock = (PREDICATELOCK *)
+ SHMQueueNext(&(oldtarget->predicateLocks),
+ &(oldtarget->predicateLocks),
+ offsetof(PREDICATELOCK, targetLink));
+ LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
+ while (oldpredlock)
+ {
+ SHM_QUEUE *predlocktargetlink;
+ PREDICATELOCK *nextpredlock;
+ PREDICATELOCK *newpredlock;
+
+ predlocktargetlink = &(oldpredlock->targetLink);
+ nextpredlock = (PREDICATELOCK *)
+ SHMQueueNext(&(oldtarget->predicateLocks),
+ predlocktargetlink,
+ offsetof(PREDICATELOCK, targetLink));
+ newpredlocktag.myXact = oldpredlock->tag.myXact;
+
+ if (removeOld)
+ {
+ SHMQueueDelete(&(oldpredlock->xactLink));
+ SHMQueueDelete(&(oldpredlock->targetLink));
+
+ hash_search_with_hash_value
+ (PredicateLockHash,
+ &oldpredlock->tag,
+ PredicateLockHashCodeFromTargetHashCode(&oldpredlock->tag,
+ oldtargettaghash),
+ HASH_REMOVE, &found);
+ Assert(found);
+ }
+
+
+ newpredlock = (PREDICATELOCK *)
+ hash_search_with_hash_value
+ (PredicateLockHash,
+ &newpredlocktag,
+ PredicateLockHashCodeFromTargetHashCode(&newpredlocktag,
+ newtargettaghash),
+ HASH_ENTER_NULL, &found);
+ if (!newpredlock)
+ {
+ /* Out of shared memory. Undo what we've done so far. */
+ LWLockRelease(SerializableXactHashLock);
+ DeleteLockTarget(newtarget, newtargettaghash);
+ outOfShmem = true;
+ goto exit;
+ }
+ SHMQueueInsertBefore(&(newtarget->predicateLocks),
+ &(newpredlock->targetLink));
+ SHMQueueInsertBefore(&(newpredlocktag.myXact->predicateLocks),
+ &(newpredlock->xactLink));
+
+ oldpredlock = nextpredlock;
+ }
+ LWLockRelease(SerializableXactHashLock);
+
+ if (removeOld)
+ {
+ Assert(SHMQueueEmpty(&oldtarget->predicateLocks));
+ RemoveTargetIfNoLongerUsed(oldtarget, oldtargettaghash);
+ }
+ }
+
+
+exit:
+ /* Release partition locks in reverse order of acquisition. */
+ if (oldpartitionLock < newpartitionLock)
+ {
+ LWLockRelease(newpartitionLock);
+ LWLockRelease(oldpartitionLock);
+ }
+ else if (oldpartitionLock > newpartitionLock)
+ {
+ LWLockRelease(oldpartitionLock);
+ LWLockRelease(newpartitionLock);
+ }
+ else
+ LWLockRelease(newpartitionLock);
+
+ if (removeOld)
+ {
+ /* We shouldn't run out of memory if we're moving locks */
+ Assert(!outOfShmem);
+
+ /* Put the reserved entry back */
+ LWLockAcquire(reservedpartitionLock, LW_EXCLUSIVE);
+ hash_search_with_hash_value(PredicateLockTargetHash,
+ &ReservedTargetTag,
+ reservedtargettaghash,
+ HASH_ENTER, &found);
+ Assert(!found);
+ LWLockRelease(reservedpartitionLock);
+ }
+
+ return !outOfShmem;
+}
+
+
+/*
+ * PredicateLockPageSplit
+ *
+ * Copies any predicate locks for the old page to the new page.
+ * Skip if this is a temporary table or toast table.
+ *
+ * NOTE: A page split (or overflow) affects all serializable transactions,
+ * even if it occurs in the context of another transaction isolation level.
+ *
+ * NOTE: This currently leaves the local copy of the locks without
+ * information on the new lock which is in shared memory. This could cause
+ * problems if enough page splits occur on locked pages without the processes
+ * which hold the locks getting in and noticing.
+ */
+void
+PredicateLockPageSplit(const Relation relation, const BlockNumber oldblkno,
+ const BlockNumber newblkno)
+{
+ PREDICATELOCKTARGETTAG oldtargettag;
+ PREDICATELOCKTARGETTAG newtargettag;
+ bool success;
+
+ if (SkipSplitTracking(relation))
+ return;
+
+ Assert(oldblkno != newblkno);
+ Assert(BlockNumberIsValid(oldblkno));
+ Assert(BlockNumberIsValid(newblkno));
+
+ SET_PREDICATELOCKTARGETTAG_PAGE(oldtargettag,
+ relation->rd_node.dbNode,
+ relation->rd_id,
+ oldblkno);
+ SET_PREDICATELOCKTARGETTAG_PAGE(newtargettag,
+ relation->rd_node.dbNode,
+ relation->rd_id,
+ newblkno);
+
+ LWLockAcquire(SerializablePredicateLockListLock, LW_EXCLUSIVE);
+
+ /*
+ * Try copying the locks over to the new page's tag, creating it if
+ * necessary.
+ */
+ success = TransferPredicateLocksToNewTarget(oldtargettag,
+ newtargettag,
+ false);
+
+ if (!success)
+ {
+ /*
+ * No more predicate lock entries are available. Failure isn't an
+ * option here, so promote the page lock to a relation lock.
+ */
+
+ /* Get the parent relation lock's lock tag */
+ success = GetParentPredicateLockTag(&oldtargettag,
+ &newtargettag);
+ Assert(success);
+
+ /* Move the locks to the parent. This shouldn't fail. */
+ success = TransferPredicateLocksToNewTarget(oldtargettag,
+ newtargettag,
+ true);
+ Assert(success);
+ }
+
+ LWLockRelease(SerializablePredicateLockListLock);
+}
+
+/*
+ * PredicateLockPageCombine
+ *
+ * Combines predicate locks for two existing pages.
+ * Skip if this is a temporary table or toast table.
+ *
+ * NOTE: A page combine affects all serializable transactions, even if it
+ * occurs in the context of another transaction isolation level.
+ */
+void
+PredicateLockPageCombine(const Relation relation, const BlockNumber oldblkno,
+ const BlockNumber newblkno)
+{
+ PREDICATELOCKTARGETTAG oldtargettag;
+ PREDICATELOCKTARGETTAG newtargettag;
+ bool success;
+
+
+ if (SkipSplitTracking(relation))
+ return;
+
+ Assert(oldblkno != newblkno);
+ Assert(BlockNumberIsValid(oldblkno));
+ Assert(BlockNumberIsValid(newblkno));
+
+ SET_PREDICATELOCKTARGETTAG_PAGE(oldtargettag,
+ relation->rd_node.dbNode,
+ relation->rd_id,
+ oldblkno);
+ SET_PREDICATELOCKTARGETTAG_PAGE(newtargettag,
+ relation->rd_node.dbNode,
+ relation->rd_id,
+ newblkno);
+
+ LWLockAcquire(SerializablePredicateLockListLock, LW_EXCLUSIVE);
+
+ /* Move the locks. This shouldn't fail. */
+ success = TransferPredicateLocksToNewTarget(oldtargettag,
+ newtargettag,
+ true);
+ Assert(success);
+
+ LWLockRelease(SerializablePredicateLockListLock);
+}
+
+/*
+ * Walk the hash table and find the new xmin.
+ */
+static void
+SetNewSxactGlobalXmin(void)
+{
+ SERIALIZABLEXACT *sxact;
+
+ Assert(LWLockHeldByMe(SerializableXactHashLock));
+
+ PredXact->SxactGlobalXmin = InvalidTransactionId;
+ PredXact->SxactGlobalXminCount = 0;
+
+ for (sxact = FirstPredXact(); sxact != NULL; sxact = NextPredXact(sxact))
+ {
+ if (!SxactIsRolledBack(sxact)
+ && !SxactIsCommitted(sxact)
+ && sxact != OldCommittedSxact)
+ {
+ Assert(sxact->xmin != InvalidTransactionId);
+ if (!TransactionIdIsValid(PredXact->SxactGlobalXmin)
+ || TransactionIdPrecedes(sxact->xmin,
+ PredXact->SxactGlobalXmin))
+ {
+ PredXact->SxactGlobalXmin = sxact->xmin;
+ PredXact->SxactGlobalXminCount = 1;
+ }
+ else if (TransactionIdEquals(sxact->xmin,
+ PredXact->SxactGlobalXmin))
+ PredXact->SxactGlobalXminCount++;
+ }
+ }
+
+ OldSerXidSetActiveSerXmin(PredXact->SxactGlobalXmin);
+}
+
+/*
+ * ReleasePredicateLocks
+ *
+ * Releases predicate locks based on completion of the current transaction,
+ * whether committed or rolled back. It can also be called for a read only
+ * transaction when it becomes impossible for the transaction to become
+ * part of a dangerous structure.
+ *
+ * We do nothing unless this is a serializable transaction.
+ *
+ * This method must ensure that shared memory hash tables are cleaned
+ * up in some relatively timely fashion.
+ *
+ * If this transaction is committing and is holding any predicate locks,
+ * it must be added to a list of completed serializable transaction still
+ * holding locks.
+ */
+void
+ReleasePredicateLocks(const bool isCommit)
+{
+ bool needToClear;
+ RWConflict conflict,
+ nextConflict,
+ possibleUnsafeConflict;
+ SERIALIZABLEXACT *roXact;
+
+ /*
+ * We can't trust XactReadOnly here, because a transaction which started
+ * as READ WRITE can show as READ ONLY later, e.g., within
+ * substransactions. We want to flag a transaction as READ ONLY if it
+ * commits without writing so that de facto READ ONLY transactions get the
+ * benefit of some RO optimizations, so we will use this local variable to
+ * get some cleanup logic right which is based on whether the transaction
+ * was declared READ ONLY at the top level.
+ */
+ bool topLevelIsDeclaredReadOnly;
+
+ if (MySerializableXact == InvalidSerializableXact)
+ {
+ Assert(LocalPredicateLockHash == NULL);
+ return;
+ }
+
+ Assert(!isCommit || SxactIsPrepared(MySerializableXact));
+ Assert(!SxactIsRolledBack(MySerializableXact));
+ Assert(!SxactIsCommitted(MySerializableXact));
+
+ /* may not be serializable during COMMIT/ROLLBACK PREPARED */
+ if (MySerializableXact->pid != 0)
+ Assert(IsolationIsSerializable());
+
+ /* We'd better not already be on the cleanup list. */
+ Assert(!SxactIsOnFinishedList((SERIALIZABLEXACT *) MySerializableXact));
+
+ topLevelIsDeclaredReadOnly = SxactIsReadOnly(MySerializableXact);
+
+ LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
+
+ /*
+ * We don't hold a lock here, assuming that TransactionId is atomic!
+ *
+ * If this value is changing, we don't care that much whether we get the
+ * old or new value -- it is just used to determine how far
+ * GlobalSerizableXmin must advance before this transaction can be cleaned
+ * fully cleaned up. The worst that could happen is we wait for ome more
+ * transaction to complete before freeing some RAM; correctness of visible
+ * behavior is not affected.
+ */
+ MySerializableXact->finishedBefore = ShmemVariableCache->nextXid;
+
+ /*
+ * If it's not a commit it's a rollback, and we can clear our locks
+ * immediately.
+ */
+ if (isCommit)
+ {
+ MySerializableXact->flags |= SXACT_FLAG_COMMITTED;
+ MySerializableXact->commitSeqNo = ++(PredXact->LastSxactCommitSeqNo);
+ /* Recognize implicit read-only transaction (commit without write). */
+ if (!(MySerializableXact->flags & SXACT_FLAG_DID_WRITE))
+ MySerializableXact->flags |= SXACT_FLAG_READ_ONLY;
+ }
+ else
+ {
+ MySerializableXact->flags |= SXACT_FLAG_ROLLED_BACK;
+ }
+
+ if (!topLevelIsDeclaredReadOnly)
+ {
+ Assert(PredXact->WritableSxactCount > 0);
+ if (--(PredXact->WritableSxactCount) == 0)
+ {
+ /*
+ * Release predicate locks and rw-conflicts in for all committed
+ * transactions. There are no longer any transactions which might
+ * conflict with the locks and no chance for new transactions to
+ * overlap. Similarly, existing conflicts in can't cause pivots,
+ * and any conflicts in which could have completed a dangerous
+ * structure would already have caused a rollback, so any
+ * remaining ones must be benign.
+ */
+ PredXact->CanPartialClearThrough = PredXact->LastSxactCommitSeqNo;
+ }
+ }
+ else
+ {
+ /*
+ * Read-only transactions: clear the list of transactions that might
+ * make us unsafe. Note that we use 'inLink' for the iteration as
+ * opposed to 'outLink' for the r/w xacts.
+ */
+ possibleUnsafeConflict = (RWConflict)
+ SHMQueueNext((SHM_QUEUE *) &MySerializableXact->possibleUnsafeConflicts,
+ (SHM_QUEUE *) &MySerializableXact->possibleUnsafeConflicts,
+ offsetof(RWConflictData, inLink));
+ while (possibleUnsafeConflict)
+ {
+ nextConflict = (RWConflict)
+ SHMQueueNext((SHM_QUEUE *) &MySerializableXact->possibleUnsafeConflicts,
+ &possibleUnsafeConflict->inLink,
+ offsetof(RWConflictData, inLink));
+
+ Assert(!SxactIsReadOnly(possibleUnsafeConflict->sxactOut));
+ Assert(MySerializableXact == possibleUnsafeConflict->sxactIn);
+
+ ReleaseRWConflict(possibleUnsafeConflict);
+
+ possibleUnsafeConflict = nextConflict;
+ }
+ }
+
+ /* Check for conflict out to old committed transactions. */
+ if (isCommit
+ && !SxactIsReadOnly(MySerializableXact)
+ && SxactHasSummaryConflictOut(MySerializableXact))
+ {
+ MySerializableXact->SeqNo.earliestOutConflictCommit =
+ FirstNormalSerCommitSeqNo;
+ MySerializableXact->flags |= SXACT_FLAG_CONFLICT_OUT;
+ }
+
+ /*
+ * Release all outConflicts to committed transactions. If we're rolling
+ * back clear them all. Set SXACT_FLAG_CONFLICT_OUT if any point to
+ * previously committed transactions.
+ */
+ conflict = (RWConflict)
+ SHMQueueNext((SHM_QUEUE *) &MySerializableXact->outConflicts,
+ (SHM_QUEUE *) &MySerializableXact->outConflicts,
+ offsetof(RWConflictData, outLink));
+ while (conflict)
+ {
+ nextConflict = (RWConflict)
+ SHMQueueNext((SHM_QUEUE *) &MySerializableXact->outConflicts,
+ &conflict->outLink,
+ offsetof(RWConflictData, outLink));
+
+ if (isCommit
+ && !SxactIsReadOnly(MySerializableXact)
+ && SxactIsCommitted(conflict->sxactIn))
+ {
+ if ((MySerializableXact->flags & SXACT_FLAG_CONFLICT_OUT) == 0
+ || conflict->sxactIn->commitSeqNo < MySerializableXact->SeqNo.earliestOutConflictCommit)
+ MySerializableXact->SeqNo.earliestOutConflictCommit = conflict->sxactIn->commitSeqNo;
+ MySerializableXact->flags |= SXACT_FLAG_CONFLICT_OUT;
+ }
+
+ if (!isCommit
+ || SxactIsCommitted(conflict->sxactIn)
+ || (conflict->sxactIn->SeqNo.lastCommitBeforeSnapshot >= PredXact->LastSxactCommitSeqNo))
+ ReleaseRWConflict(conflict);
+
+ conflict = nextConflict;
+ }
+
+ /*
+ * Release all inConflicts from committed and read-only transactions. If
+ * we're rolling back, clear them all.
+ */
+ conflict = (RWConflict)
+ SHMQueueNext((SHM_QUEUE *) &MySerializableXact->inConflicts,
+ (SHM_QUEUE *) &MySerializableXact->inConflicts,
+ offsetof(RWConflictData, inLink));
+ while (conflict)
+ {
+ nextConflict = (RWConflict)
+ SHMQueueNext((SHM_QUEUE *) &MySerializableXact->inConflicts,
+ &conflict->inLink,
+ offsetof(RWConflictData, inLink));
+
+ if (!isCommit
+ || SxactIsCommitted(conflict->sxactOut)
+ || SxactIsReadOnly(conflict->sxactOut))
+ ReleaseRWConflict(conflict);
+
+ conflict = nextConflict;
+ }
+
+ if (!topLevelIsDeclaredReadOnly)
+ {
+ /*
+ * Remove ourselves from the list of possible conflicts for concurrent
+ * READ ONLY transactions, flagging them as unsafe if we have a
+ * conflict out. If any are waiting DEFERRABLE transactions, wake them
+ * up if they are known safe or known unsafe.
+ */
+ possibleUnsafeConflict = (RWConflict)
+ SHMQueueNext((SHM_QUEUE *) &MySerializableXact->possibleUnsafeConflicts,
+ (SHM_QUEUE *) &MySerializableXact->possibleUnsafeConflicts,
+ offsetof(RWConflictData, outLink));
+ while (possibleUnsafeConflict)
+ {
+ nextConflict = (RWConflict)
+ SHMQueueNext((SHM_QUEUE *) &MySerializableXact->possibleUnsafeConflicts,
+ &possibleUnsafeConflict->outLink,
+ offsetof(RWConflictData, outLink));
+
+ roXact = possibleUnsafeConflict->sxactIn;
+ Assert(MySerializableXact == possibleUnsafeConflict->sxactOut);
+ Assert(SxactIsReadOnly(roXact));
+
+ /* Mark conflicted if necessary. */
+ if (isCommit
+ && (MySerializableXact->flags & SXACT_FLAG_DID_WRITE)
+ && SxactHasConflictOut(MySerializableXact)
+ && (MySerializableXact->SeqNo.earliestOutConflictCommit
+ <= roXact->SeqNo.lastCommitBeforeSnapshot))
+ {
+ /*
+ * This releases possibleUnsafeConflict (as well as all other
+ * possible conflicts for roXact)
+ */
+ FlagSxactUnsafe(roXact);
+ }
+ else
+ {
+ ReleaseRWConflict(possibleUnsafeConflict);
+
+ /*
+ * If we were the last possible conflict, flag it safe. The
+ * transaction can now safely release its predicate locks (but
+ * that transaction's backend has to do that itself).
+ */
+ if (SHMQueueEmpty(&roXact->possibleUnsafeConflicts))
+ roXact->flags |= SXACT_FLAG_RO_SAFE;
+ }
+
+ /*
+ * Wake up the process for a waiting DEFERRABLE transaction if we
+ * now know it's either safe or conflicted.
+ */
+ if (SxactIsDeferrableWaiting(roXact) &&
+ (SxactIsROUnsafe(roXact) || SxactIsROSafe(roXact)))
+ ProcSendSignal(roXact->pid);
+
+ possibleUnsafeConflict = nextConflict;
+ }
+ }
+
+ /*
+ * Check whether it's time to clean up old transactions. This can only be
+ * done when the last serializable transaction with the oldest xmin among
+ * serializable transactions completes. We then find the "new oldest"
+ * xmin and purge any transactions which finished before this transaction
+ * was launched.
+ */
+ needToClear = false;
+ if (TransactionIdEquals(MySerializableXact->xmin, PredXact->SxactGlobalXmin))
+ {
+ Assert(PredXact->SxactGlobalXminCount > 0);
+ if (--(PredXact->SxactGlobalXminCount) == 0)
+ {
+ SetNewSxactGlobalXmin();
+ needToClear = true;
+ }
+ }
+
+ LWLockRelease(SerializableXactHashLock);
+
+ LWLockAcquire(SerializableFinishedListLock, LW_EXCLUSIVE);
+
+ /* Add this to the list of transactions to check for later cleanup. */
+ if (isCommit)
+ SHMQueueInsertBefore(FinishedSerializableTransactions,
+ (SHM_QUEUE *) &(MySerializableXact->finishedLink));
+
+ if (!isCommit)
+ ReleaseOneSerializableXact((SERIALIZABLEXACT *) MySerializableXact,
+ false, false);
+
+ LWLockRelease(SerializableFinishedListLock);
+
+ if (needToClear)
+ ClearOldPredicateLocks();
+
+ MySerializableXact = InvalidSerializableXact;
+
+ /* Delete per-transaction lock table */
+ if (LocalPredicateLockHash != NULL)
+ {
+ hash_destroy(LocalPredicateLockHash);
+ LocalPredicateLockHash = NULL;
+ }
+}
+
+/*
+ * ReleasePredicateLocksIfROSafe
+ * Check if the current transaction is read only and operating on
+ * a safe snapshot. If so, release predicate locks and return
+ * true.
+ *
+ * A transaction is flagged as RO_SAFE if all concurrent R/W
+ * transactions commit without having conflicts out to an earlier
+ * snapshot, thus ensuring that no conflicts are possible for this
+ * transaction. Thus, we call this function as part of the
+ * SkipSerialization check on all public interface methods.
+ */
+static bool
+ReleasePredicateLocksIfROSafe(void)
+{
+ if (SxactIsROSafe(MySerializableXact))
+ {
+ ReleasePredicateLocks(false);
+ return true;
+ }
+ else
+ return false;
+}
+
+/*
+ * Clear old predicate locks.
+ */
+static void
+ClearOldPredicateLocks(void)
+{
+ SERIALIZABLEXACT *finishedSxact;
+ PREDICATELOCK *predlock;
+ int i;
+ HASH_SEQ_STATUS seqstat;
+ PREDICATELOCKTARGET *locktarget;
+
+ LWLockAcquire(SerializableFinishedListLock, LW_EXCLUSIVE);
+ finishedSxact = (SERIALIZABLEXACT *)
+ SHMQueueNext(FinishedSerializableTransactions,
+ FinishedSerializableTransactions,
+ offsetof(SERIALIZABLEXACT, finishedLink));
+ LWLockAcquire(SerializableXactHashLock, LW_SHARED);
+ while (finishedSxact)
+ {
+ SERIALIZABLEXACT *nextSxact;
+
+ nextSxact = (SERIALIZABLEXACT *)
+ SHMQueueNext(FinishedSerializableTransactions,
+ &(finishedSxact->finishedLink),
+ offsetof(SERIALIZABLEXACT, finishedLink));
+ if (!TransactionIdIsValid(PredXact->SxactGlobalXmin)
+ || TransactionIdPrecedesOrEquals(finishedSxact->finishedBefore,
+ PredXact->SxactGlobalXmin))
+ {
+ LWLockRelease(SerializableXactHashLock);
+ SHMQueueDelete(&(finishedSxact->finishedLink));
+ ReleaseOneSerializableXact(finishedSxact, false, false);
+ LWLockAcquire(SerializableXactHashLock, LW_SHARED);
+ }
+ else if (finishedSxact->commitSeqNo > PredXact->HavePartialClearedThrough
+ && finishedSxact->commitSeqNo <= PredXact->CanPartialClearThrough)
+ {
+ LWLockRelease(SerializableXactHashLock);
+ ReleaseOneSerializableXact(finishedSxact,
+ !SxactIsReadOnly(finishedSxact),
+ false);
+ PredXact->HavePartialClearedThrough = finishedSxact->commitSeqNo;
+ LWLockAcquire(SerializableXactHashLock, LW_SHARED);
+ }
+ else
+ break;
+ finishedSxact = nextSxact;
+ }
+ LWLockRelease(SerializableXactHashLock);
+
+ /*
+ * Loop through predicate locks on dummy transaction for summarized data.
+ */
+ predlock = (PREDICATELOCK *)
+ SHMQueueNext(&OldCommittedSxact->predicateLocks,
+ &OldCommittedSxact->predicateLocks,
+ offsetof(PREDICATELOCK, xactLink));
+ LWLockAcquire(SerializablePredicateLockListLock, LW_SHARED);
+ while (predlock)
+ {
+ PREDICATELOCK *nextpredlock;
+ bool canDoPartialCleanup;
+
+ nextpredlock = (PREDICATELOCK *)
+ SHMQueueNext(&OldCommittedSxact->predicateLocks,
+ &predlock->xactLink,
+ offsetof(PREDICATELOCK, xactLink));
+
+ LWLockAcquire(SerializableXactHashLock, LW_SHARED);
+ canDoPartialCleanup = (predlock->commitSeqNo <= PredXact->CanPartialClearThrough);
+ LWLockRelease(SerializableXactHashLock);
+
+ if (canDoPartialCleanup)
+ {
+ PREDICATELOCKTAG tag;
+ SHM_QUEUE *targetLink;
+ PREDICATELOCKTARGET *target;
+ PREDICATELOCKTARGETTAG targettag;
+ uint32 targettaghash;
+ LWLockId partitionLock;
+
+ tag = predlock->tag;
+ targetLink = &(predlock->targetLink);
+ target = tag.myTarget;
+ targettag = target->tag;
+ targettaghash = PredicateLockTargetTagHashCode(&targettag);
+ partitionLock = PredicateLockHashPartitionLock(targettaghash);
+
+ LWLockAcquire(partitionLock, LW_EXCLUSIVE);
+
+ SHMQueueDelete(targetLink);
+ SHMQueueDelete(&(predlock->xactLink));
+
+ hash_search_with_hash_value(PredicateLockHash, &tag,
+ PredicateLockHashCodeFromTargetHashCode(&tag,
+ targettaghash),
+ HASH_REMOVE, NULL);
+ RemoveTargetIfNoLongerUsed(target, targettaghash);
+
+ LWLockRelease(partitionLock);
+ }
+
+ predlock = nextpredlock;
+ }
+
+ LWLockRelease(SerializablePredicateLockListLock);
+ LWLockRelease(SerializableFinishedListLock);
+
+ if (!PredXact->NeedTargetLinkCleanup)
+ return;
+
+ /*
+ * Clean up any targets which were disconnected from a prior version with
+ * no predicate locks attached.
+ */
+ for (i = 0; i < NUM_PREDICATELOCK_PARTITIONS; i++)
+ LWLockAcquire(FirstPredicateLockMgrLock + i, LW_EXCLUSIVE);
+ LWLockAcquire(PredicateLockNextRowLinkLock, LW_SHARED);
+
+ hash_seq_init(&seqstat, PredicateLockTargetHash);
+ while ((locktarget = (PREDICATELOCKTARGET *) hash_seq_search(&seqstat)))
+ {
+ if (SHMQueueEmpty(&locktarget->predicateLocks)
+ && locktarget->priorVersionOfRow == NULL
+ && locktarget->nextVersionOfRow == NULL)
+ {
+ hash_search(PredicateLockTargetHash, &locktarget->tag,
+ HASH_REMOVE, NULL);
+ }
+ }
+
+ PredXact->NeedTargetLinkCleanup = false;
+
+ LWLockRelease(PredicateLockNextRowLinkLock);
+ for (i = NUM_PREDICATELOCK_PARTITIONS - 1; i >= 0; i--)
+ LWLockRelease(FirstPredicateLockMgrLock + i);
+}
+
+/*
+ * This is the normal way to delete anything from any of the predicate
+ * locking hash tables. Given a transaction which we know can be deleted:
+ * delete all predicate locks held by that transaction and any predicate
+ * lock targets which are now unreferenced by a lock; delete all conflicts
+ * for the transaction; delete all xid values for the transaction; then
+ * delete the transaction.
+ *
+ * When the partial flag is set, we can release all predicate locks and
+ * out-conflict information -- we've established that there are no longer
+ * any overlapping read write transactions for which this transaction could
+ * matter.
+ *
+ * When the summarize flag is set, we've run short of room for sxact data
+ * and must summarize to the SLRU. Predicate locks are transferred to a
+ * dummy "old" transaction, with duplicate locks on a single target
+ * collapsing to a single lock with the "latest" commitSeqNo from among
+ * the conflicting locks..
+ */
+static void
+ReleaseOneSerializableXact(SERIALIZABLEXACT *sxact, bool partial,
+ bool summarize)
+{
+ PREDICATELOCK *predlock;
+ SERIALIZABLEXIDTAG sxidtag;
+ RWConflict conflict,
+ nextConflict;
+
+ Assert(sxact != NULL);
+ Assert(SxactIsRolledBack(sxact) || SxactIsCommitted(sxact));
+ Assert(LWLockHeldByMe(SerializableFinishedListLock));
+
+ LWLockAcquire(SerializablePredicateLockListLock, LW_SHARED);
+ predlock = (PREDICATELOCK *)
+ SHMQueueNext(&(sxact->predicateLocks),
+ &(sxact->predicateLocks),
+ offsetof(PREDICATELOCK, xactLink));
+ while (predlock)
+ {
+ PREDICATELOCK *nextpredlock;
+ PREDICATELOCKTAG tag;
+ SHM_QUEUE *targetLink;
+ PREDICATELOCKTARGET *target;
+ PREDICATELOCKTARGETTAG targettag;
+ uint32 targettaghash;
+ LWLockId partitionLock;
+
+ nextpredlock = (PREDICATELOCK *)
+ SHMQueueNext(&(sxact->predicateLocks),
+ &(predlock->xactLink),
+ offsetof(PREDICATELOCK, xactLink));
+
+ tag = predlock->tag;
+ targetLink = &(predlock->targetLink);
+ target = tag.myTarget;
+ targettag = target->tag;
+ targettaghash = PredicateLockTargetTagHashCode(&targettag);
+ partitionLock = PredicateLockHashPartitionLock(targettaghash);
+
+ LWLockAcquire(partitionLock, LW_EXCLUSIVE);
+
+ SHMQueueDelete(targetLink);
+
+ hash_search_with_hash_value(PredicateLockHash, &tag,
+ PredicateLockHashCodeFromTargetHashCode(&tag,
+ targettaghash),
+ HASH_REMOVE, NULL);
+ if (summarize)
+ {
+ bool found;
+
+ /* Fold into dummy transaction list. */
+ tag.myXact = OldCommittedSxact;
+ predlock = hash_search_with_hash_value(PredicateLockHash, &tag,
+ PredicateLockHashCodeFromTargetHashCode(&tag,
+ targettaghash),
+ HASH_ENTER, &found);
+ if (!predlock)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_predicate_locks_per_transaction.")));
+ if (found)
+ {
+ if (predlock->commitSeqNo < sxact->commitSeqNo)
+ predlock->commitSeqNo = sxact->commitSeqNo;
+ }
+ else
+ {
+ SHMQueueInsertBefore(&(target->predicateLocks),
+ &(predlock->targetLink));
+ SHMQueueInsertBefore(&(OldCommittedSxact->predicateLocks),
+ &(predlock->xactLink));
+ predlock->commitSeqNo = sxact->commitSeqNo;
+ }
+ }
+ else
+ RemoveTargetIfNoLongerUsed(target, targettaghash);
+
+ LWLockRelease(partitionLock);
+
+ predlock = nextpredlock;
+ }
+
+ /*
+ * Rather than retail removal, just re-init the head after we've run
+ * through the list.
+ */
+ SHMQueueInit(&sxact->predicateLocks);
+
+ LWLockRelease(SerializablePredicateLockListLock);
+
+ sxidtag.xid = sxact->topXid;
+ LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
+
+ if (!partial)
+ {
+ /* Release all outConflicts. */
+ conflict = (RWConflict)
+ SHMQueueNext(&sxact->outConflicts,
+ &sxact->outConflicts,
+ offsetof(RWConflictData, outLink));
+ while (conflict)
+ {
+ nextConflict = (RWConflict)
+ SHMQueueNext(&sxact->outConflicts,
+ &conflict->outLink,
+ offsetof(RWConflictData, outLink));
+ if (summarize)
+ conflict->sxactIn->flags |= SXACT_FLAG_SUMMARY_CONFLICT_IN;
+ ReleaseRWConflict(conflict);
+ conflict = nextConflict;
+ }
+ }
+
+ /* Release all inConflicts. */
+ conflict = (RWConflict)
+ SHMQueueNext(&sxact->inConflicts,
+ &sxact->inConflicts,
+ offsetof(RWConflictData, inLink));
+ while (conflict)
+ {
+ nextConflict = (RWConflict)
+ SHMQueueNext(&sxact->inConflicts,
+ &conflict->inLink,
+ offsetof(RWConflictData, inLink));
+ if (summarize)
+ conflict->sxactOut->flags |= SXACT_FLAG_SUMMARY_CONFLICT_OUT;
+ ReleaseRWConflict(conflict);
+ conflict = nextConflict;
+ }
+
+ if (!partial)
+ {
+ /* Get rid of the xid and the record of the transaction itself. */
+ if (sxidtag.xid != InvalidTransactionId)
+ hash_search(SerializableXidHash, &sxidtag, HASH_REMOVE, NULL);
+ ReleasePredXact(sxact);
+ }
+
+ LWLockRelease(SerializableXactHashLock);
+}
+
+/*
+ * Tests whether the given top level transaction is concurrent with
+ * (overlaps) our current transaction.
+ *
+ * We need to identify the top level transaction for SSI, anyway, so pass
+ * that to this function to save the overhead of checking the snapshot's
+ * subxip array.
+ */
+static bool
+XidIsConcurrent(TransactionId xid)
+{
+ Snapshot snap;
+ uint32 i;
+
+ Assert(TransactionIdIsValid(xid));
+ Assert(!TransactionIdEquals(xid, GetTopTransactionIdIfAny()));
+
+ snap = GetTransactionSnapshot();
+
+ if (TransactionIdPrecedes(xid, snap->xmin))
+ return false;
+
+ if (TransactionIdFollowsOrEquals(xid, snap->xmax))
+ return true;
+
+ for (i = 0; i < snap->xcnt; i++)
+ {
+ if (xid == snap->xip[i])
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * CheckForSerializableConflictOut
+ * We are reading a tuple which has been modified. If it is visible to
+ * us but has been deleted, that indicates a rw-conflict out. If it's
+ * not visible and was created by a concurrent (overlapping)
+ * serializable transaction, that is also a rw-conflict out,
+ *
+ * We will determine the top level xid of the writing transaction with which
+ * we may be in conflict, and check for overlap with our own transaction.
+ * If the transactions overlap (i.e., they cannot see each other's writes),
+ * then we have a conflict out.
+ *
+ * This function should be called just about anywhere in heapam.c that a
+ * tuple has been read. There is currently no known reason to call this
+ * function from an index AM.
+ */
+void
+CheckForSerializableConflictOut(const bool visible, const Relation relation,
+ const HeapTuple tuple, const Buffer buffer)
+{
+ TransactionId xid;
+ SERIALIZABLEXIDTAG sxidtag;
+ SERIALIZABLEXID *sxid;
+ SERIALIZABLEXACT *sxact;
+ HTSV_Result htsvResult;
+
+ if (SkipSerialization(relation))
+ return;
+
+ if (SxactIsMarkedForDeath(MySerializableXact))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+ errmsg("could not serialize access due to read/write dependencies among transactions"),
+ errdetail("Cancelled on identification as a pivot, during conflict out checking."),
+ errhint("The transaction might succeed if retried.")));
+ }
+
+ /*
+ * Check to see whether the tuple has been written to by a concurrent
+ * transaction, either to create it not visible to us, or to delete it
+ * while it is visible to us. The "visible" bool indicates whether the
+ * tuple is visible to us, while HeapTupleSatisfiesVacuum checks what else
+ * is going on with it.
+ */
+ htsvResult = HeapTupleSatisfiesVacuum(tuple->t_data, TransactionXmin, buffer);
+ switch (htsvResult)
+ {
+ case HEAPTUPLE_LIVE:
+ if (visible)
+ return;
+ xid = HeapTupleHeaderGetXmin(tuple->t_data);
+ break;
+ case HEAPTUPLE_RECENTLY_DEAD:
+ if (!visible)
+ return;
+ xid = HeapTupleHeaderGetXmax(tuple->t_data);
+ break;
+ case HEAPTUPLE_DELETE_IN_PROGRESS:
+ xid = HeapTupleHeaderGetXmax(tuple->t_data);
+ break;
+ case HEAPTUPLE_INSERT_IN_PROGRESS:
+ xid = HeapTupleHeaderGetXmin(tuple->t_data);
+ break;
+ case HEAPTUPLE_DEAD:
+ return;
+ default:
+
+ /*
+ * The only way to get to this default clause is if a new value is
+ * added to the enum type without adding it to this switch
+ * statement. That's a bug, so elog.
+ */
+ elog(ERROR, "unrecognized return value from HeapTupleSatisfiesVacuum: %u", htsvResult);
+
+ /*
+ * In spite of having all enum values covered and calling elog on
+ * this default, some compilers think this is a code path which
+ * allows xid to be used below without initialization. Silence
+ * that warning.
+ */
+ xid = InvalidTransactionId;
+ }
+ Assert(TransactionIdIsValid(xid));
+ Assert(TransactionIdFollowsOrEquals(xid, TransactionXmin));
+
+ /*
+ * Find top level xid. Bail out if xid is too early to be a conflict, or
+ * if it's our own xid.
+ */
+ if (TransactionIdEquals(xid, GetTopTransactionIdIfAny()))
+ return;
+ xid = SubTransGetTopmostTransaction(xid);
+ if (TransactionIdPrecedes(xid, TransactionXmin))
+ return;
+ if (TransactionIdEquals(xid, GetTopTransactionIdIfAny()))
+ return;
+
+ /*
+ * Find sxact or summarized info for the top level xid.
+ */
+ sxidtag.xid = xid;
+ LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
+ sxid = (SERIALIZABLEXID *)
+ hash_search(SerializableXidHash, &sxidtag, HASH_FIND, NULL);
+ if (!sxid)
+ {
+ /*
+ * Transaction not found in "normal" SSI structures. Check whether it
+ * got pushed out to SLRU storage for "old committed" transactions.
+ */
+ SerCommitSeqNo conflictCommitSeqNo;
+
+ conflictCommitSeqNo = OldSerXidGetMinConflictCommitSeqNo(xid);
+ if (conflictCommitSeqNo != 0)
+ {
+ if (conflictCommitSeqNo != InvalidSerCommitSeqNo
+ && (!SxactIsReadOnly(MySerializableXact)
+ || conflictCommitSeqNo
+ <= MySerializableXact->SeqNo.lastCommitBeforeSnapshot))
+ ereport(ERROR,
+ (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+ errmsg("could not serialize access due to read/write dependencies among transactions"),
+ errdetail("Cancelled on conflict out to old pivot %u.", xid),
+ errhint("The transaction might succeed if retried.")));
+
+ if (SxactHasSummaryConflictIn(MySerializableXact)
+ || !SHMQueueEmpty((SHM_QUEUE *) &MySerializableXact->inConflicts))
+ ereport(ERROR,
+ (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+ errmsg("could not serialize access due to read/write dependencies among transactions"),
+ errdetail("Cancelled on identification as a pivot, with conflict out to old committed transaction %u.", xid),
+ errhint("The transaction might succeed if retried.")));
+
+ MySerializableXact->flags |= SXACT_FLAG_SUMMARY_CONFLICT_OUT;
+ }
+
+ /* It's not serializable or otherwise not important. */
+ LWLockRelease(SerializableXactHashLock);
+ return;
+ }
+ sxact = sxid->myXact;
+ Assert(TransactionIdEquals(sxact->topXid, xid));
+ if (sxact == MySerializableXact
+ || SxactIsRolledBack(sxact)
+ || SxactIsMarkedForDeath(sxact))
+ {
+ /* We can't conflict with our own transaction or one rolled back. */
+ LWLockRelease(SerializableXactHashLock);
+ return;
+ }
+
+ /*
+ * We have a conflict out to a transaction which has a conflict out to a
+ * summarized transaction. That summarized transaction must have
+ * committed first, and we can't tell when it committed in relation to our
+ * snapshot acquisition, so something needs to be cancelled.
+ */
+ if (SxactHasSummaryConflictOut(sxact))
+ {
+ if (!SxactIsPrepared(sxact))
+ {
+ sxact->flags |= SXACT_FLAG_MARKED_FOR_DEATH;
+ LWLockRelease(SerializableXactHashLock);
+ return;
+ }
+ else
+ {
+ LWLockRelease(SerializableXactHashLock);
+ ereport(ERROR,
+ (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+ errmsg("could not serialize access due to read/write dependencies among transactions"),
+ errdetail("Cancelled on conflict out to old pivot."),
+ errhint("The transaction might succeed if retried.")));
+ }
+ }
+
+ /*
+ * If this is a read-only transaction and the writing transaction has
+ * committed, and it doesn't have a rw-conflict to a transaction which
+ * committed before it, no conflict.
+ */
+ if (SxactIsReadOnly(MySerializableXact)
+ && SxactIsCommitted(sxact)
+ && !SxactHasSummaryConflictOut(sxact)
+ && (!SxactHasConflictOut(sxact)
+ || MySerializableXact->SeqNo.lastCommitBeforeSnapshot < sxact->SeqNo.earliestOutConflictCommit))
+ {
+ /* Read-only transaction will appear to run first. No conflict. */
+ LWLockRelease(SerializableXactHashLock);
+ return;
+ }
+
+ if (!XidIsConcurrent(xid))
+ {
+ /* This write was already in our snapshot; no conflict. */
+ LWLockRelease(SerializableXactHashLock);
+ return;
+ }
+
+ if (RWConflictExists((SERIALIZABLEXACT *) MySerializableXact, sxact))
+ {
+ /* We don't want duplicate conflict records in the list. */
+ LWLockRelease(SerializableXactHashLock);
+ return;
+ }
+
+ /*
+ * Flag the conflict. But first, if this conflict creates a dangerous
+ * structure, ereport an error.
+ */
+ FlagRWConflict((SERIALIZABLEXACT *) MySerializableXact, sxact);
+ LWLockRelease(SerializableXactHashLock);
+}
+
+/*
+ * Check a particular target for rw-dependency conflict in. This will
+ * also check prior versions of a tuple, if any.
+ */
+static void
+CheckTargetForConflictsIn(PREDICATELOCKTARGETTAG *targettag)
+{
+ PREDICATELOCKTARGETTAG nexttargettag;
+ PREDICATELOCKTARGETTAG thistargettag;
+
+ for (;;)
+ {
+ if (!CheckSingleTargetForConflictsIn(targettag, &nexttargettag))
+ break;
+ thistargettag = nexttargettag;
+ targettag = &thistargettag;
+ }
+}
+
+/*
+ * Check a particular target for rw-dependency conflict in. If the tuple
+ * has prior versions, returns true and *nexttargettag is set to the tag
+ * of the prior tuple version.
+ */
+static bool
+CheckSingleTargetForConflictsIn(PREDICATELOCKTARGETTAG *targettag,
+ PREDICATELOCKTARGETTAG *nexttargettag)
+{
+ uint32 targettaghash;
+ LWLockId partitionLock;
+ PREDICATELOCKTARGET *target;
+ PREDICATELOCK *predlock;
+ bool hasnexttarget = false;
+
+ Assert(MySerializableXact != InvalidSerializableXact);
+
+ /*
+ * The same hash and LW lock apply to the lock target and the lock itself.
+ */
+ targettaghash = PredicateLockTargetTagHashCode(targettag);
+ partitionLock = PredicateLockHashPartitionLock(targettaghash);
+ LWLockAcquire(partitionLock, LW_SHARED);
+ LWLockAcquire(PredicateLockNextRowLinkLock, LW_SHARED);
+ target = (PREDICATELOCKTARGET *)
+ hash_search_with_hash_value(PredicateLockTargetHash,
+ targettag, targettaghash,
+ HASH_FIND, NULL);
+ if (!target)
+ {
+ /* Nothing has this target locked; we're done here. */
+ LWLockRelease(PredicateLockNextRowLinkLock);
+ LWLockRelease(partitionLock);
+ return false;
+ }
+
+ /*
+ * If the target is linked to a prior version of the row, save the tag so
+ * that it can be used for iterative calls to this function.
+ */
+ if (target->priorVersionOfRow != NULL)
+ {
+ *nexttargettag = target->priorVersionOfRow->tag;
+ hasnexttarget = true;
+ }
+ LWLockRelease(PredicateLockNextRowLinkLock);
+
+ /*
+ * Each lock for an overlapping transaction represents a conflict: a
+ * rw-dependency in to this transaction.
+ */
+ predlock = (PREDICATELOCK *)
+ SHMQueueNext(&(target->predicateLocks),
+ &(target->predicateLocks),
+ offsetof(PREDICATELOCK, targetLink));
+ LWLockAcquire(SerializableXactHashLock, LW_SHARED);
+ while (predlock)
+ {
+ SHM_QUEUE *predlocktargetlink;
+ PREDICATELOCK *nextpredlock;
+ SERIALIZABLEXACT *sxact;
+
+ predlocktargetlink = &(predlock->targetLink);
+ nextpredlock = (PREDICATELOCK *)
+ SHMQueueNext(&(target->predicateLocks),
+ predlocktargetlink,
+ offsetof(PREDICATELOCK, targetLink));
+
+ sxact = predlock->tag.myXact;
+ if (sxact == MySerializableXact)
+ {
+ /*
+ * If we're getting a write lock on the tuple, we don't need a
+ * predicate (SIREAD) lock. At this point our transaction already
+ * has an ExclusiveRowLock on the relation, so we are OK to drop
+ * the predicate lock on the tuple, if found, without fearing that
+ * another write against the tuple will occur before the MVCC
+ * information makes it to the buffer.
+ */
+ if (GET_PREDICATELOCKTARGETTAG_OFFSET(*targettag))
+ {
+ uint32 predlockhashcode;
+ PREDICATELOCKTARGET *rmtarget = NULL;
+ PREDICATELOCK *rmpredlock;
+ LOCALPREDICATELOCK *locallock,
+ *rmlocallock;
+
+ /*
+ * This is a tuple on which we have a tuple predicate lock. We
+ * only have shared LW locks now; release those, and get
+ * exclusive locks only while we modify things.
+ */
+ LWLockRelease(SerializableXactHashLock);
+ LWLockRelease(partitionLock);
+ LWLockAcquire(SerializablePredicateLockListLock, LW_SHARED);
+ LWLockAcquire(partitionLock, LW_EXCLUSIVE);
+ LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
+
+ /*
+ * Remove the predicate lock from shared memory, if it wasn't
+ * removed while the locks were released. One way that could
+ * happen is from autovacuum cleaning up an index.
+ */
+ predlockhashcode = PredicateLockHashCodeFromTargetHashCode
+ (&(predlock->tag), targettaghash);
+ rmpredlock = (PREDICATELOCK *)
+ hash_search_with_hash_value(PredicateLockHash,
+ &(predlock->tag),
+ predlockhashcode,
+ HASH_FIND, NULL);
+ if (rmpredlock)
+ {
+ Assert(rmpredlock == predlock);
+
+ SHMQueueDelete(predlocktargetlink);
+ SHMQueueDelete(&(predlock->xactLink));
+
+ rmpredlock = (PREDICATELOCK *)
+ hash_search_with_hash_value(PredicateLockHash,
+ &(predlock->tag),
+ predlockhashcode,
+ HASH_REMOVE, NULL);
+ Assert(rmpredlock == predlock);
+
+ RemoveTargetIfNoLongerUsed(target, targettaghash);
+
+ LWLockRelease(SerializableXactHashLock);
+ LWLockRelease(partitionLock);
+ LWLockRelease(SerializablePredicateLockListLock);
+
+ locallock = (LOCALPREDICATELOCK *)
+ hash_search_with_hash_value(LocalPredicateLockHash,
+ targettag, targettaghash,
+ HASH_FIND, NULL);
+ Assert(locallock != NULL);
+ Assert(locallock->held);
+ locallock->held = false;
+
+ if (locallock->childLocks == 0)
+ {
+ rmlocallock = (LOCALPREDICATELOCK *)
+ hash_search_with_hash_value(LocalPredicateLockHash,
+ targettag, targettaghash,
+ HASH_REMOVE, NULL);
+ Assert(rmlocallock == locallock);
+ }
+
+ DecrementParentLocks(targettag);
+
+ /*
+ * If we've cleaned up the last of the predicate locks for
+ * the target, bail out before re-acquiring the locks.
+ */
+ if (rmtarget)
+ return hasnexttarget;
+
+ /*
+ * The list has been altered. Start over at the front.
+ */
+ LWLockAcquire(partitionLock, LW_SHARED);
+ nextpredlock = (PREDICATELOCK *)
+ SHMQueueNext(&(target->predicateLocks),
+ &(target->predicateLocks),
+ offsetof(PREDICATELOCK, targetLink));
+
+ LWLockAcquire(SerializableXactHashLock, LW_SHARED);
+ }
+ else
+ {
+ /*
+ * The predicate lock was cleared while we were attempting
+ * to upgrade our lightweight locks. Revert to the shared
+ * locks.
+ */
+ LWLockRelease(SerializableXactHashLock);
+ LWLockRelease(partitionLock);
+ LWLockRelease(SerializablePredicateLockListLock);
+ LWLockAcquire(partitionLock, LW_SHARED);
+ LWLockAcquire(SerializableXactHashLock, LW_SHARED);
+ }
+ }
+ }
+ else if (!SxactIsRolledBack(sxact)
+ && (!SxactIsCommitted(sxact)
+ || TransactionIdPrecedes(GetTransactionSnapshot()->xmin,
+ sxact->finishedBefore))
+ && !RWConflictExists(sxact, (SERIALIZABLEXACT *) MySerializableXact))
+ {
+ LWLockRelease(SerializableXactHashLock);
+ LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
+
+ FlagRWConflict(sxact, (SERIALIZABLEXACT *) MySerializableXact);
+
+ LWLockRelease(SerializableXactHashLock);
+ LWLockAcquire(SerializableXactHashLock, LW_SHARED);
+ }
+
+ predlock = nextpredlock;
+ }
+ LWLockRelease(SerializableXactHashLock);
+ LWLockRelease(partitionLock);
+
+ return hasnexttarget;
+}
+
+/*
+ * CheckForSerializableConflictIn
+ * We are writing the given tuple. If that indicates a rw-conflict
+ * in from another serializable transaction, take appropriate action.
+ *
+ * Skip checking for any granularity for which a parameter is missing.
+ *
+ * A tuple update or delete is in conflict if we have a predicate lock
+ * against the relation or page in which the tuple exists, or against the
+ * tuple itself.
+ */
+void
+CheckForSerializableConflictIn(const Relation relation, const HeapTuple tuple,
+ const Buffer buffer)
+{
+ PREDICATELOCKTARGETTAG targettag;
+
+ if (SkipSerialization(relation))
+ return;
+
+ if (SxactIsMarkedForDeath(MySerializableXact))
+ ereport(ERROR,
+ (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+ errmsg("could not serialize access due to read/write dependencies among transactions"),
+ errdetail("Cancelled on identification as a pivot, during conflict in checking."),
+ errhint("The transaction might succeed if retried.")));
+
+ MySerializableXact->flags |= SXACT_FLAG_DID_WRITE;
+
+ /*
+ * It is important that we check for locks from the finest granularity to
+ * the coarsest granularity, so that granularity promotion doesn't cause
+ * us to miss a lock. The new (coarser) lock will be acquired before the
+ * old (finer) locks are released.
+ *
+ * It is not possible to take and hold a lock across the checks for all
+ * granularities because each target could be in a separate partition.
+ */
+ if (tuple != NULL)
+ {
+ SET_PREDICATELOCKTARGETTAG_TUPLE(targettag,
+ relation->rd_node.dbNode,
+ relation->rd_id,
+ ItemPointerGetBlockNumber(&(tuple->t_data->t_ctid)),
+ ItemPointerGetOffsetNumber(&(tuple->t_data->t_ctid)));
+ CheckTargetForConflictsIn(&targettag);
+ }
+
+ if (BufferIsValid(buffer))
+ {
+ SET_PREDICATELOCKTARGETTAG_PAGE(targettag,
+ relation->rd_node.dbNode,
+ relation->rd_id,
+ BufferGetBlockNumber(buffer));
+ CheckTargetForConflictsIn(&targettag);
+ }
+
+ SET_PREDICATELOCKTARGETTAG_RELATION(targettag,
+ relation->rd_node.dbNode,
+ relation->rd_id);
+ CheckTargetForConflictsIn(&targettag);
+}
+
+/*
+ * Flag a rw-dependency between two serializable transactions.
+ *
+ * The caller is responsible for ensuring that we have a LW lock on
+ * the transaction hash table.
+ */
+static void
+FlagRWConflict(SERIALIZABLEXACT *reader, SERIALIZABLEXACT *writer)
+{
+ Assert(reader != writer);
+
+ /* First, see if this conflict causes failure. */
+ OnConflict_CheckForSerializationFailure(reader, writer);
+
+ /* Actually do the conflict flagging. */
+ if (reader == OldCommittedSxact)
+ writer->flags |= SXACT_FLAG_SUMMARY_CONFLICT_IN;
+ else if (writer == OldCommittedSxact)
+ reader->flags |= SXACT_FLAG_SUMMARY_CONFLICT_OUT;
+ else
+ SetRWConflict(reader, writer);
+}
+
+/*
+ * Check whether we should roll back one of these transactions
+ * instead of flagging a new rw-conflict.
+ */
+static void
+OnConflict_CheckForSerializationFailure(const SERIALIZABLEXACT *reader,
+ SERIALIZABLEXACT *writer)
+{
+ bool failure;
+ RWConflict conflict;
+
+ Assert(LWLockHeldByMe(SerializableXactHashLock));
+
+ failure = false;
+
+ /*
+ * Check for already-committed writer with rw-conflict out flagged. This
+ * means that the reader must immediately fail.
+ */
+ if (SxactIsCommitted(writer)
+ && (SxactHasConflictOut(writer) || SxactHasSummaryConflictOut(writer)))
+ failure = true;
+
+ /*
+ * Check whether the reader has become a pivot with a committed writer. If
+ * so, we must roll back unless every in-conflict either committed before
+ * the writer committed or is READ ONLY and overlaps the writer.
+ */
+ if (!failure && SxactIsCommitted(writer) && !SxactIsReadOnly(reader))
+ {
+ if (SxactHasSummaryConflictIn(reader))
+ {
+ failure = true;
+ conflict = NULL;
+ }
+ else
+ conflict = (RWConflict)
+ SHMQueueNext(&reader->inConflicts,
+ &reader->inConflicts,
+ offsetof(RWConflictData, inLink));
+ while (conflict)
+ {
+ if (!SxactIsRolledBack(conflict->sxactOut)
+ && (!SxactIsCommitted(conflict->sxactOut)
+ || conflict->sxactOut->commitSeqNo >= writer->commitSeqNo)
+ && (!SxactIsReadOnly(conflict->sxactOut)
+ || conflict->sxactOut->SeqNo.lastCommitBeforeSnapshot >= writer->commitSeqNo))
+ {
+ failure = true;
+ break;
+ }
+ conflict = (RWConflict)
+ SHMQueueNext(&reader->inConflicts,
+ &conflict->inLink,
+ offsetof(RWConflictData, inLink));
+ }
+ }
+
+ /*
+ * Check whether the writer has become a pivot with an out-conflict
+ * committed transaction, while neither reader nor writer is committed. If
+ * the reader is a READ ONLY transaction, there is only a serialization
+ * failure if an out-conflict transaction causing the pivot committed
+ * before the reader acquired its snapshot. (That is, the reader must not
+ * have been concurrent with the out-conflict transaction.)
+ */
+ if (!failure && !SxactIsCommitted(writer))
+ {
+ if (SxactHasSummaryConflictOut(reader))
+ {
+ failure = true;
+ conflict = NULL;
+ }
+ else
+ conflict = (RWConflict)
+ SHMQueueNext(&writer->outConflicts,
+ &writer->outConflicts,
+ offsetof(RWConflictData, outLink));
+ while (conflict)
+ {
+ if ((reader == conflict->sxactIn && SxactIsCommitted(reader))
+ || (SxactIsCommitted(conflict->sxactIn)
+ && !SxactIsCommitted(reader)
+ && (!SxactIsReadOnly(reader)
+ || conflict->sxactIn->commitSeqNo <= reader->SeqNo.lastCommitBeforeSnapshot)))
+ {
+ failure = true;
+ break;
+ }
+ conflict = (RWConflict)
+ SHMQueueNext(&writer->outConflicts,
+ &conflict->outLink,
+ offsetof(RWConflictData, outLink));
+ }
+ }
+
+ if (failure)
+ {
+ if (MySerializableXact == writer)
+ {
+ LWLockRelease(SerializableXactHashLock);
+ ereport(ERROR,
+ (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+ errmsg("could not serialize access due to read/write dependencies among transactions"),
+ errdetail("Cancelled on identification as pivot, during write."),
+ errhint("The transaction might succeed if retried.")));
+ }
+ else if (SxactIsPrepared(writer))
+ {
+ LWLockRelease(SerializableXactHashLock);
+ ereport(ERROR,
+ (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+ errmsg("could not serialize access due to read/write dependencies among transactions"),
+ errdetail("Cancelled on conflict out to pivot %u, during read.", writer->topXid),
+ errhint("The transaction might succeed if retried.")));
+ }
+ writer->flags |= SXACT_FLAG_MARKED_FOR_DEATH;
+ }
+}
+
+/*
+ * PreCommit_CheckForSerializableConflicts
+ * Check for dangerous structures in a serializable transaction
+ * at commit.
+ *
+ * We're checking for a dangerous structure as each conflict is recorded.
+ * The only way we could have a problem at commit is if this is the "out"
+ * side of a pivot, and neither the "in" side nor the pivot has yet
+ * committed.
+ *
+ * If a dangerous structure is found, the pivot (the near conflict) is
+ * marked for death, because rolling back another transaction might mean
+ * that we flail without ever making progress. This transaction is
+ * committing writes, so letting it commit ensures progress. If we
+ * cancelled the far conflict, it might immediately fail again on retry.
+ */
+void
+PreCommit_CheckForSerializationFailure(void)
+{
+ RWConflict nearConflict;
+
+ if (MySerializableXact == InvalidSerializableXact)
+ return;
+
+ Assert(IsolationIsSerializable());
+
+ LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
+
+ if (SxactIsMarkedForDeath(MySerializableXact))
+ {
+ LWLockRelease(SerializableXactHashLock);
+ ereport(ERROR,
+ (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+ errmsg("could not serialize access due to read/write dependencies among transactions"),
+ errdetail("Cancelled on identification as a pivot, during commit attempt."),
+ errhint("The transaction might succeed if retried.")));
+ }
+
+ nearConflict = (RWConflict)
+ SHMQueueNext((SHM_QUEUE *) &MySerializableXact->inConflicts,
+ (SHM_QUEUE *) &MySerializableXact->inConflicts,
+ offsetof(RWConflictData, inLink));
+ while (nearConflict)
+ {
+ if (!SxactIsCommitted(nearConflict->sxactOut)
+ && !SxactIsRolledBack(nearConflict->sxactOut)
+ && !SxactIsMarkedForDeath(nearConflict->sxactOut))
+ {
+ RWConflict farConflict;
+
+ farConflict = (RWConflict)
+ SHMQueueNext(&nearConflict->sxactOut->inConflicts,
+ &nearConflict->sxactOut->inConflicts,
+ offsetof(RWConflictData, inLink));
+ while (farConflict)
+ {
+ if (farConflict->sxactOut == MySerializableXact
+ || (!SxactIsCommitted(farConflict->sxactOut)
+ && !SxactIsReadOnly(farConflict->sxactOut)
+ && !SxactIsRolledBack(farConflict->sxactOut)
+ && !SxactIsMarkedForDeath(farConflict->sxactOut)))
+ {
+ nearConflict->sxactOut->flags |= SXACT_FLAG_MARKED_FOR_DEATH;
+ break;
+ }
+ farConflict = (RWConflict)
+ SHMQueueNext(&nearConflict->sxactOut->inConflicts,
+ &farConflict->inLink,
+ offsetof(RWConflictData, inLink));
+ }
+ }
+
+ nearConflict = (RWConflict)
+ SHMQueueNext((SHM_QUEUE *) &MySerializableXact->inConflicts,
+ &nearConflict->inLink,
+ offsetof(RWConflictData, inLink));
+ }
+
+ MySerializableXact->flags |= SXACT_FLAG_PREPARED;
+
+ LWLockRelease(SerializableXactHashLock);
+}
+
+/*------------------------------------------------------------------------*/
+
+/*
+ * Two-phase commit support
+ */
+
+/*
+ * AtPrepare_Locks
+ * Do the preparatory work for a PREPARE: make 2PC state file
+ * records for all predicate locks currently held.
+ */
+void
+AtPrepare_PredicateLocks(void)
+{
+ PREDICATELOCK *predlock;
+ SERIALIZABLEXACT *sxact;
+ TwoPhasePredicateRecord record;
+ TwoPhasePredicateXactRecord *xactRecord;
+ TwoPhasePredicateLockRecord *lockRecord;
+
+ sxact = (SERIALIZABLEXACT *) MySerializableXact;
+ xactRecord = &(record.data.xactRecord);
+ lockRecord = &(record.data.lockRecord);
+
+ if (MySerializableXact == InvalidSerializableXact)
+ return;
+
+ /* Generate a xact record for our SERIALIZABLEXACT */
+ record.type = TWOPHASEPREDICATERECORD_XACT;
+ xactRecord->xmin = MySerializableXact->xmin;
+ xactRecord->flags = MySerializableXact->flags;
+
+ /*
+ * Tweak the flags. Since we're not going to output the inConflicts and
+ * outConflicts lists, if they're non-empty we'll represent that by
+ * setting the appropriate summary conflict flags.
+ */
+ if (!SHMQueueEmpty((SHM_QUEUE *) &MySerializableXact->inConflicts))
+ xactRecord->flags |= SXACT_FLAG_SUMMARY_CONFLICT_IN;
+ if (!SHMQueueEmpty((SHM_QUEUE *) &MySerializableXact->outConflicts))
+ xactRecord->flags |= SXACT_FLAG_SUMMARY_CONFLICT_OUT;
+
+ RegisterTwoPhaseRecord(TWOPHASE_RM_PREDICATELOCK_ID, 0,
+ &record, sizeof(record));
+
+ /*
+ * Generate a lock record for each lock.
+ *
+ * To do this, we need to walk the predicate lock list in our sxact rather
+ * than using the local predicate lock table because the latter is not
+ * guaranteed to be accurate.
+ */
+ LWLockAcquire(SerializablePredicateLockListLock, LW_SHARED);
+
+ predlock = (PREDICATELOCK *)
+ SHMQueueNext(&(sxact->predicateLocks),
+ &(sxact->predicateLocks),
+ offsetof(PREDICATELOCK, xactLink));
+
+ while (predlock != NULL)
+ {
+ record.type = TWOPHASEPREDICATERECORD_LOCK;
+ lockRecord->target = predlock->tag.myTarget->tag;
+
+ RegisterTwoPhaseRecord(TWOPHASE_RM_PREDICATELOCK_ID, 0,
+ &record, sizeof(record));
+
+ predlock = (PREDICATELOCK *)
+ SHMQueueNext(&(sxact->predicateLocks),
+ &(predlock->xactLink),
+ offsetof(PREDICATELOCK, xactLink));
+ }
+
+ LWLockRelease(SerializablePredicateLockListLock);
+}
+
+/*
+ * PostPrepare_Locks
+ * Clean up after successful PREPARE. Unlike the non-predicate
+ * lock manager, we do not need to transfer locks to a dummy
+ * PGPROC because our SERIALIZABLEXACT will stay around
+ * anyway. We only need to clean up our local state.
+ */
+void
+PostPrepare_PredicateLocks(TransactionId xid)
+{
+ if (MySerializableXact == InvalidSerializableXact)
+ return;
+
+ Assert(SxactIsPrepared(MySerializableXact));
+
+ MySerializableXact->pid = 0;
+
+ hash_destroy(LocalPredicateLockHash);
+ LocalPredicateLockHash = NULL;
+
+ MySerializableXact = InvalidSerializableXact;
+}
+
+/*
+ * PredicateLockTwoPhaseFinish
+ * Release a prepared transaction's predicate locks once it
+ * commits or aborts.
+ */
+void
+PredicateLockTwoPhaseFinish(TransactionId xid, bool isCommit)
+{
+ SERIALIZABLEXID *sxid;
+ SERIALIZABLEXIDTAG sxidtag;
+
+ sxidtag.xid = xid;
+
+ LWLockAcquire(SerializableXactHashLock, LW_SHARED);
+ sxid = (SERIALIZABLEXID *)
+ hash_search(SerializableXidHash, &sxidtag, HASH_FIND, NULL);
+ LWLockRelease(SerializableXactHashLock);
+
+ /* xid will not be found if it wasn't a serializable transaction */
+ if (sxid == NULL)
+ return;
+
+ /* Release its locks */
+ MySerializableXact = sxid->myXact;
+ ReleasePredicateLocks(isCommit);
+}
+
+/*
+ * Re-acquire a predicate lock belonging to a transaction that was prepared.
+ */
+void
+predicatelock_twophase_recover(TransactionId xid, uint16 info,
+ void *recdata, uint32 len)
+{
+ TwoPhasePredicateRecord *record;
+
+ Assert(len == sizeof(TwoPhasePredicateRecord));
+
+ record = (TwoPhasePredicateRecord *) recdata;
+
+ Assert((record->type == TWOPHASEPREDICATERECORD_XACT) ||
+ (record->type == TWOPHASEPREDICATERECORD_LOCK));
+
+ if (record->type == TWOPHASEPREDICATERECORD_XACT)
+ {
+ /* Per-transaction record. Set up a SERIALIZABLEXACT. */
+ TwoPhasePredicateXactRecord *xactRecord;
+ SERIALIZABLEXACT *sxact;
+ SERIALIZABLEXID *sxid;
+ SERIALIZABLEXIDTAG sxidtag;
+ bool found;
+
+ xactRecord = (TwoPhasePredicateXactRecord *) &record->data.xactRecord;
+
+ LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
+ sxact = CreatePredXact();
+ if (!sxact)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory")));
+
+ /* vxid for a prepared xact is InvalidBackendId/xid; no pid */
+ sxact->vxid.backendId = InvalidBackendId;
+ sxact->vxid.localTransactionId = (LocalTransactionId) xid;
+ sxact->pid = 0;
+
+ /* a prepared xact hasn't committed yet */
+ sxact->commitSeqNo = InvalidSerCommitSeqNo;
+ sxact->finishedBefore = InvalidTransactionId;
+
+ sxact->SeqNo.lastCommitBeforeSnapshot = RecoverySerCommitSeqNo;
+
+
+ /*
+ * We don't need the details of a prepared transaction's conflicts,
+ * just whether it had conflicts in or out (which we get from the
+ * flags)
+ */
+ SHMQueueInit(&(sxact->outConflicts));
+ SHMQueueInit(&(sxact->inConflicts));
+
+ /*
+ * Don't need to track this; no transactions running at the time the
+ * recovered xact started are still active, except possibly other
+ * prepared xacts and we don't care whether those are RO_SAFE or not.
+ */
+ SHMQueueInit(&(sxact->possibleUnsafeConflicts));
+
+ SHMQueueInit(&(sxact->predicateLocks));
+ SHMQueueElemInit(&(sxact->finishedLink));
+
+ sxact->topXid = xid;
+ sxact->xmin = xactRecord->xmin;
+ sxact->flags = xactRecord->flags;
+ Assert(SxactIsPrepared(sxact));
+ if (!SxactIsReadOnly(sxact))
+ {
+ ++(PredXact->WritableSxactCount);
+ Assert(PredXact->WritableSxactCount <=
+ (MaxBackends + max_prepared_xacts));
+ }
+
+ /* Register the transaction's xid */
+ sxidtag.xid = xid;
+ sxid = (SERIALIZABLEXID *) hash_search(SerializableXidHash,
+ &sxidtag,
+ HASH_ENTER, &found);
+ if (!sxid)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory")));
+ Assert(!found);
+ sxid->myXact = (SERIALIZABLEXACT *) sxact;
+
+ /*
+ * Update global xmin. Note that this is a special case compared to
+ * registering a normal transaction, because the global xmin might go
+ * backwards. That's OK, because until recovery is over we're not
+ * going to complete any transactions or create any non-prepared
+ * transactions, so there's no danger of throwing away.
+ */
+ if ((!TransactionIdIsValid(PredXact->SxactGlobalXmin)) ||
+ (TransactionIdFollows(PredXact->SxactGlobalXmin, sxact->xmin)))
+ {
+ PredXact->SxactGlobalXmin = sxact->xmin;
+ PredXact->SxactGlobalXminCount = 1;
+ OldSerXidSetActiveSerXmin(sxact->xmin);
+ }
+ else if (TransactionIdEquals(sxact->xmin, PredXact->SxactGlobalXmin))
+ {
+ Assert(PredXact->SxactGlobalXminCount > 0);
+ PredXact->SxactGlobalXminCount++;
+ }
+
+ LWLockRelease(SerializableXactHashLock);
+ }
+ else if (record->type == TWOPHASEPREDICATERECORD_LOCK)
+ {
+ /* Lock record. Recreate the PREDICATELOCK */
+ TwoPhasePredicateLockRecord *lockRecord;
+ SERIALIZABLEXID *sxid;
+ SERIALIZABLEXACT *sxact;
+ SERIALIZABLEXIDTAG sxidtag;
+ uint32 targettaghash;
+
+ lockRecord = (TwoPhasePredicateLockRecord *) &record->data.lockRecord;
+ targettaghash = PredicateLockTargetTagHashCode(&lockRecord->target);
+
+ LWLockAcquire(SerializableXactHashLock, LW_SHARED);
+ sxidtag.xid = xid;
+ sxid = (SERIALIZABLEXID *)
+ hash_search(SerializableXidHash, &sxidtag, HASH_FIND, NULL);
+ LWLockRelease(SerializableXactHashLock);
+
+ Assert(sxid != NULL);
+ sxact = sxid->myXact;
+ Assert(sxact != InvalidSerializableXact);
+
+ CreatePredicateLock(&lockRecord->target, targettaghash, sxact);
+ }
+}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 95000377704..af2eba01d61 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -374,6 +374,10 @@ standard_ProcessUtility(Node *parsetree,
SetPGVariable("transaction_read_only",
list_make1(item->arg),
true);
+ else if (strcmp(item->defname, "transaction_deferrable") == 0)
+ SetPGVariable("transaction_deferrable",
+ list_make1(item->arg),
+ true);
}
}
break;
diff --git a/src/backend/utils/adt/lockfuncs.c b/src/backend/utils/adt/lockfuncs.c
index 78cafada2c8..8e369826cec 100644
--- a/src/backend/utils/adt/lockfuncs.c
+++ b/src/backend/utils/adt/lockfuncs.c
@@ -15,6 +15,7 @@
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "storage/predicate_internals.h"
#include "storage/proc.h"
#include "utils/builtins.h"
@@ -32,11 +33,20 @@ static const char *const LockTagTypeNames[] = {
"advisory"
};
+/* This must match enum PredicateLockTargetType (predicate_internals.h) */
+static const char *const PredicateLockTagTypeNames[] = {
+ "relation",
+ "page",
+ "tuple"
+};
+
/* Working status for pg_lock_status */
typedef struct
{
LockData *lockData; /* state data from lmgr */
int currIdx; /* current PROCLOCK index */
+ PredicateLockData *predLockData; /* state data for pred locks */
+ int predLockIdx; /* current index for pred lock */
} PG_Lock_Status;
@@ -69,6 +79,7 @@ pg_lock_status(PG_FUNCTION_ARGS)
FuncCallContext *funcctx;
PG_Lock_Status *mystatus;
LockData *lockData;
+ PredicateLockData *predLockData;
if (SRF_IS_FIRSTCALL())
{
@@ -126,6 +137,8 @@ pg_lock_status(PG_FUNCTION_ARGS)
mystatus->lockData = GetLockStatusData();
mystatus->currIdx = 0;
+ mystatus->predLockData = GetPredicateLockStatusData();
+ mystatus->predLockIdx = 0;
MemoryContextSwitchTo(oldcontext);
}
@@ -303,6 +316,72 @@ pg_lock_status(PG_FUNCTION_ARGS)
SRF_RETURN_NEXT(funcctx, result);
}
+ /*
+ * Have returned all regular locks. Now start on the SIREAD predicate
+ * locks.
+ */
+ predLockData = mystatus->predLockData;
+ if (mystatus->predLockIdx < predLockData->nelements)
+ {
+ PredicateLockTargetType lockType;
+
+ PREDICATELOCKTARGETTAG *predTag = &(predLockData->locktags[mystatus->predLockIdx]);
+ SERIALIZABLEXACT *xact = &(predLockData->xacts[mystatus->predLockIdx]);
+ Datum values[14];
+ bool nulls[14];
+ HeapTuple tuple;
+ Datum result;
+
+ mystatus->predLockIdx++;
+
+ /*
+ * Form tuple with appropriate data.
+ */
+ MemSet(values, 0, sizeof(values));
+ MemSet(nulls, false, sizeof(nulls));
+
+ /* lock type */
+ lockType = GET_PREDICATELOCKTARGETTAG_TYPE(*predTag);
+
+ values[0] = CStringGetTextDatum(PredicateLockTagTypeNames[lockType]);
+
+ /* lock target */
+ values[1] = GET_PREDICATELOCKTARGETTAG_DB(*predTag);
+ values[2] = GET_PREDICATELOCKTARGETTAG_RELATION(*predTag);
+ if (lockType == PREDLOCKTAG_TUPLE)
+ values[4] = GET_PREDICATELOCKTARGETTAG_OFFSET(*predTag);
+ else
+ nulls[4] = true;
+ if ((lockType == PREDLOCKTAG_TUPLE) ||
+ (lockType == PREDLOCKTAG_PAGE))
+ values[3] = GET_PREDICATELOCKTARGETTAG_PAGE(*predTag);
+ else
+ nulls[3] = true;
+
+ /* these fields are targets for other types of locks */
+ nulls[5] = true; /* virtualxid */
+ nulls[6] = true; /* transactionid */
+ nulls[7] = true; /* classid */
+ nulls[8] = true; /* objid */
+ nulls[9] = true; /* objsubid */
+
+ /* lock holder */
+ values[10] = VXIDGetDatum(xact->vxid.backendId,
+ xact->vxid.localTransactionId);
+ nulls[11] = true; /* pid */
+
+ /*
+ * Lock mode. Currently all predicate locks are SIReadLocks, which are
+ * always held (never waiting)
+ */
+ values[12] = CStringGetTextDatum("SIReadLock");
+ values[13] = BoolGetDatum(true);
+
+ tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
+ result = HeapTupleGetDatum(tuple);
+ SRF_RETURN_NEXT(funcctx, result);
+ }
+
SRF_RETURN_DONE(funcctx);
}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 2c95ef80c45..216236b5294 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -59,6 +59,7 @@
#include "storage/bufmgr.h"
#include "storage/standby.h"
#include "storage/fd.h"
+#include "storage/predicate.h"
#include "tcop/tcopprot.h"
#include "tsearch/ts_cache.h"
#include "utils/builtins.h"
@@ -1097,6 +1098,23 @@ static struct config_bool ConfigureNamesBool[] =
false, assign_transaction_read_only, NULL
},
{
+ {"default_transaction_deferrable", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ gettext_noop("Sets the default deferrable status of new transactions."),
+ NULL
+ },
+ &DefaultXactDeferrable,
+ false, NULL, NULL
+ },
+ {
+ {"transaction_deferrable", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ gettext_noop("Whether to defer a read-only serializable transaction until it can be executed with no possible serialization failures."),
+ NULL,
+ GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+ },
+ &XactDeferrable,
+ false, assign_transaction_deferrable, NULL
+ },
+ {
{"check_function_bodies", PGC_USERSET, CLIENT_CONN_STATEMENT,
gettext_noop("Check function bodies during CREATE FUNCTION."),
NULL
@@ -1696,6 +1714,17 @@ static struct config_int ConfigureNamesInt[] =
},
{
+ {"max_predicate_locks_per_transaction", PGC_POSTMASTER, LOCK_MANAGEMENT,
+ gettext_noop("Sets the maximum number of predicate locks per transaction."),
+ gettext_noop("The shared predicate lock table is sized on the assumption that "
+ "at most max_predicate_locks_per_transaction * max_connections distinct "
+ "objects will need to be locked at any one time.")
+ },
+ &max_predicate_locks_per_xact,
+ 64, 10, INT_MAX, NULL, NULL
+ },
+
+ {
{"authentication_timeout", PGC_SIGHUP, CONN_AUTH_SECURITY,
gettext_noop("Sets the maximum allowed time to complete client authentication."),
NULL,
@@ -3460,6 +3489,8 @@ InitializeGUCOptions(void)
PGC_POSTMASTER, PGC_S_OVERRIDE);
SetConfigOption("transaction_read_only", "no",
PGC_POSTMASTER, PGC_S_OVERRIDE);
+ SetConfigOption("transaction_deferrable", "no",
+ PGC_POSTMASTER, PGC_S_OVERRIDE);
/*
* For historical reasons, some GUC parameters can receive defaults from
@@ -5699,6 +5730,9 @@ ExecSetVariableStmt(VariableSetStmt *stmt)
else if (strcmp(item->defname, "transaction_read_only") == 0)
SetPGVariable("transaction_read_only",
list_make1(item->arg), stmt->is_local);
+ else if (strcmp(item->defname, "transaction_deferrable") == 0)
+ SetPGVariable("transaction_deferrable",
+ list_make1(item->arg), stmt->is_local);
else
elog(ERROR, "unexpected SET TRANSACTION element: %s",
item->defname);
@@ -5718,6 +5752,9 @@ ExecSetVariableStmt(VariableSetStmt *stmt)
else if (strcmp(item->defname, "transaction_read_only") == 0)
SetPGVariable("default_transaction_read_only",
list_make1(item->arg), stmt->is_local);
+ else if (strcmp(item->defname, "transaction_deferrable") == 0)
+ SetPGVariable("default_transaction_deferrable",
+ list_make1(item->arg), stmt->is_local);
else
elog(ERROR, "unexpected SET SESSION element: %s",
item->defname);
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 6c6f9a9a0d8..fe80c4dc239 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -450,6 +450,7 @@
#check_function_bodies = on
#default_transaction_isolation = 'read committed'
#default_transaction_read_only = off
+#default_transaction_deferrable = off
#session_replication_role = 'origin'
#statement_timeout = 0 # in milliseconds, 0 is disabled
#vacuum_freeze_min_age = 50000000
@@ -501,7 +502,8 @@
# Note: Each lock table slot uses ~270 bytes of shared memory, and there are
# max_locks_per_transaction * (max_connections + max_prepared_transactions)
# lock table slots.
-
+#max_predicate_locks_per_transaction = 64 # min 10
+ # (change requires restart)
#------------------------------------------------------------------------------
# VERSION/PLATFORM COMPATIBILITY
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index ef1cac01477..c1ba5ad8e64 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -22,6 +22,7 @@
#include "access/hash.h"
#include "storage/bufmgr.h"
+#include "storage/predicate.h"
#include "storage/proc.h"
#include "utils/memutils.h"
#include "utils/rel.h"
@@ -261,7 +262,10 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
* the top of the recursion.
*/
if (owner == TopTransactionResourceOwner)
+ {
ProcReleaseLocks(isCommit);
+ ReleasePredicateLocks(isCommit);
+ }
}
else
{
diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c
index 45b92a0ef88..c2ff5e542b0 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -27,6 +27,7 @@
#include "access/transam.h"
#include "access/xact.h"
+#include "storage/predicate.h"
#include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/memutils.h"
@@ -126,9 +127,6 @@ GetTransactionSnapshot(void)
{
Assert(RegisteredSnapshots == 0);
- CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData);
- FirstSnapshotSet = true;
-
/*
* In transaction-snapshot mode, the first snapshot must live until
* end of xact regardless of what the caller does with it, so we must
@@ -136,11 +134,20 @@ GetTransactionSnapshot(void)
*/
if (IsolationUsesXactSnapshot())
{
- CurrentSnapshot = RegisterSnapshotOnOwner(CurrentSnapshot,
+ if (IsolationIsSerializable())
+ CurrentSnapshot = RegisterSerializableTransaction(&CurrentSnapshotData);
+ else
+ {
+ CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData);
+ CurrentSnapshot = RegisterSnapshotOnOwner(CurrentSnapshot,
TopTransactionResourceOwner);
+ }
registered_xact_snapshot = true;
}
+ else
+ CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData);
+ FirstSnapshotSet = true;
return CurrentSnapshot;
}
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 73d5a62d85e..b903b7b0575 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -2299,6 +2299,7 @@ main(int argc, char *argv[])
"pg_xlog/archive_status",
"pg_clog",
"pg_notify",
+ "pg_serial",
"pg_subtrans",
"pg_twophase",
"pg_multixact/members",
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e844b5b0624..d3eb7662880 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -11,14 +11,14 @@
* script that reproduces the schema in terms of SQL that is understood
* by PostgreSQL
*
- * Note that pg_dump runs in a serializable transaction, so it sees a
- * consistent snapshot of the database including system catalogs.
- * However, it relies in part on various specialized backend functions
- * like pg_get_indexdef(), and those things tend to run on SnapshotNow
- * time, ie they look at the currently committed state. So it is
- * possible to get 'cache lookup failed' error if someone performs DDL
- * changes while a dump is happening. The window for this sort of thing
- * is from the beginning of the serializable transaction to
+ * Note that pg_dump runs in a transaction-snapshot mode transaction,
+ * so it sees a consistent snapshot of the database including system
+ * catalogs. However, it relies in part on various specialized backend
+ * functions like pg_get_indexdef(), and those things tend to run on
+ * SnapshotNow time, ie they look at the currently committed state. So
+ * it is possible to get 'cache lookup failed' error if someone
+ * performs DDL changes while a dump is happening. The window for this
+ * sort of thing is from the acquisition of the transaction snapshot to
* getSchemaData() (when pg_dump acquires AccessShareLock on every
* table it intends to dump). It isn't very large, but it can happen.
*
@@ -135,6 +135,7 @@ static int dump_inserts = 0;
static int column_inserts = 0;
static int no_security_label = 0;
static int no_unlogged_table_data = 0;
+static int serializable_deferrable = 0;
static void help(const char *progname);
@@ -318,6 +319,7 @@ main(int argc, char **argv)
{"no-tablespaces", no_argument, &outputNoTablespaces, 1},
{"quote-all-identifiers", no_argument, &quote_all_identifiers, 1},
{"role", required_argument, NULL, 3},
+ {"serializable-deferrable", no_argument, &serializable_deferrable, 1},
{"use-set-session-authorization", no_argument, &use_setsessauth, 1},
{"no-security-label", no_argument, &no_security_label, 1},
{"no-unlogged-table-data", no_argument, &no_unlogged_table_data, 1},
@@ -669,11 +671,21 @@ main(int argc, char **argv)
no_security_label = 1;
/*
- * Start serializable transaction to dump consistent data.
+ * Start transaction-snapshot mode transaction to dump consistent data.
*/
do_sql_command(g_conn, "BEGIN");
-
- do_sql_command(g_conn, "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE");
+ if (g_fout->remoteVersion >= 90100)
+ {
+ if (serializable_deferrable)
+ do_sql_command(g_conn,
+ "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE, "
+ "READ ONLY, DEFERRABLE");
+ else
+ do_sql_command(g_conn,
+ "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ");
+ }
+ else
+ do_sql_command(g_conn, "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE");
/* Select the appropriate subquery to convert user IDs to names */
if (g_fout->remoteVersion >= 80100)
@@ -864,6 +876,7 @@ help(const char *progname)
printf(_(" --disable-triggers disable triggers during data-only restore\n"));
printf(_(" --no-tablespaces do not dump tablespace assignments\n"));
printf(_(" --quote-all-identifiers quote all identifiers, even if not keywords\n"));
+ printf(_(" --serializable-deferrable wait until the dump can run without anomalies\n"));
printf(_(" --role=ROLENAME do SET ROLE before dump\n"));
printf(_(" --no-security-label do not dump security label assignments\n"));
printf(_(" --no-unlogged-table-data do not dump unlogged table data\n"));
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 9efab4c5003..4dbc3937099 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -82,8 +82,8 @@ extern HeapTuple heap_getnext(HeapScanDesc scan, ScanDirection direction);
extern bool heap_fetch(Relation relation, Snapshot snapshot,
HeapTuple tuple, Buffer *userbuf, bool keep_buf,
Relation stats_relation);
-extern bool heap_hot_search_buffer(ItemPointer tid, Buffer buffer,
- Snapshot snapshot, bool *all_dead);
+extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
+ Buffer buffer, Snapshot snapshot, bool *all_dead);
extern bool heap_hot_search(ItemPointer tid, Relation relation,
Snapshot snapshot, bool *all_dead);
diff --git a/src/include/access/relscan.h b/src/include/access/relscan.h
index 989dc576856..f703280b272 100644
--- a/src/include/access/relscan.h
+++ b/src/include/access/relscan.h
@@ -35,6 +35,7 @@ typedef struct HeapScanDescData
BlockNumber rs_startblock; /* block # to start at */
BufferAccessStrategy rs_strategy; /* access strategy for reads */
bool rs_syncscan; /* report location to syncscan logic? */
+ bool rs_relpredicatelocked; /* predicate lock on relation exists */
/* scan current state */
bool rs_inited; /* false = scan not init'd yet */
diff --git a/src/include/access/twophase_rmgr.h b/src/include/access/twophase_rmgr.h
index a541d0fce79..1c7d8bb4c03 100644
--- a/src/include/access/twophase_rmgr.h
+++ b/src/include/access/twophase_rmgr.h
@@ -23,8 +23,9 @@ typedef uint8 TwoPhaseRmgrId;
*/
#define TWOPHASE_RM_END_ID 0
#define TWOPHASE_RM_LOCK_ID 1
-#define TWOPHASE_RM_PGSTAT_ID 2
-#define TWOPHASE_RM_MULTIXACT_ID 3
+#define TWOPHASE_RM_PREDICATELOCK_ID 2
+#define TWOPHASE_RM_PGSTAT_ID 3
+#define TWOPHASE_RM_MULTIXACT_ID 4
#define TWOPHASE_RM_MAX_ID TWOPHASE_RM_MULTIXACT_ID
extern const TwoPhaseCallback twophase_recover_callbacks[];
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 902e99e815c..1685a0167f5 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -32,15 +32,26 @@ extern int DefaultXactIsoLevel;
extern int XactIsoLevel;
/*
- * We only implement two isolation levels internally. This macro should
- * be used to check which one is selected.
+ * We implement three isolation levels internally.
+ * The two stronger ones use one snapshot per database transaction;
+ * the others use one snapshot per statement.
+ * Serializable uses predicate locks in addition to snapshots.
+ * These macros should be used to check which isolation level is selected.
*/
#define IsolationUsesXactSnapshot() (XactIsoLevel >= XACT_REPEATABLE_READ)
+#define IsolationIsSerializable() (XactIsoLevel == XACT_SERIALIZABLE)
/* Xact read-only state */
extern bool DefaultXactReadOnly;
extern bool XactReadOnly;
+/*
+ * Xact is deferrable -- only meaningful (currently) for read only
+ * SERIALIZABLE transactions
+ */
+extern bool DefaultXactDeferrable;
+extern bool XactDeferrable;
+
/* Asynchronous commits */
extern bool XactSyncCommit;
diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h
index 16fbdd629a9..566d4fac5dd 100644
--- a/src/include/catalog/pg_am.h
+++ b/src/include/catalog/pg_am.h
@@ -49,6 +49,7 @@ CATALOG(pg_am,2601)
bool amsearchnulls; /* can AM search for NULL/NOT NULL entries? */
bool amstorage; /* can storage type differ from column type? */
bool amclusterable; /* does AM support cluster command? */
+ bool ampredlocks; /* does AM handle predicate locks? */
Oid amkeytype; /* type of data in index, or InvalidOid */
regproc aminsert; /* "insert this tuple" function */
regproc ambeginscan; /* "prepare for index scan" function */
@@ -77,7 +78,7 @@ typedef FormData_pg_am *Form_pg_am;
* compiler constants for pg_am
* ----------------
*/
-#define Natts_pg_am 27
+#define Natts_pg_am 28
#define Anum_pg_am_amname 1
#define Anum_pg_am_amstrategies 2
#define Anum_pg_am_amsupport 3
@@ -90,37 +91,38 @@ typedef FormData_pg_am *Form_pg_am;
#define Anum_pg_am_amsearchnulls 10
#define Anum_pg_am_amstorage 11
#define Anum_pg_am_amclusterable 12
-#define Anum_pg_am_amkeytype 13
-#define Anum_pg_am_aminsert 14
-#define Anum_pg_am_ambeginscan 15
-#define Anum_pg_am_amgettuple 16
-#define Anum_pg_am_amgetbitmap 17
-#define Anum_pg_am_amrescan 18
-#define Anum_pg_am_amendscan 19
-#define Anum_pg_am_ammarkpos 20
-#define Anum_pg_am_amrestrpos 21
-#define Anum_pg_am_ambuild 22
-#define Anum_pg_am_ambuildempty 23
-#define Anum_pg_am_ambulkdelete 24
-#define Anum_pg_am_amvacuumcleanup 25
-#define Anum_pg_am_amcostestimate 26
-#define Anum_pg_am_amoptions 27
+#define Anum_pg_am_ampredlocks 13
+#define Anum_pg_am_amkeytype 14
+#define Anum_pg_am_aminsert 15
+#define Anum_pg_am_ambeginscan 16
+#define Anum_pg_am_amgettuple 17
+#define Anum_pg_am_amgetbitmap 18
+#define Anum_pg_am_amrescan 19
+#define Anum_pg_am_amendscan 20
+#define Anum_pg_am_ammarkpos 21
+#define Anum_pg_am_amrestrpos 22
+#define Anum_pg_am_ambuild 23
+#define Anum_pg_am_ambuildempty 24
+#define Anum_pg_am_ambulkdelete 25
+#define Anum_pg_am_amvacuumcleanup 26
+#define Anum_pg_am_amcostestimate 27
+#define Anum_pg_am_amoptions 28
/* ----------------
* initial contents of pg_am
* ----------------
*/
-DATA(insert OID = 403 ( btree 5 1 t f t t t t t f t 0 btinsert btbeginscan btgettuple btgetbitmap btrescan btendscan btmarkpos btrestrpos btbuild btbuildempty btbulkdelete btvacuumcleanup btcostestimate btoptions ));
+DATA(insert OID = 403 ( btree 5 1 t f t t t t t f t t 0 btinsert btbeginscan btgettuple btgetbitmap btrescan btendscan btmarkpos btrestrpos btbuild btbuildempty btbulkdelete btvacuumcleanup btcostestimate btoptions ));
DESCR("b-tree index access method");
#define BTREE_AM_OID 403
-DATA(insert OID = 405 ( hash 1 1 f f t f f f f f f 23 hashinsert hashbeginscan hashgettuple hashgetbitmap hashrescan hashendscan hashmarkpos hashrestrpos hashbuild hashbuildempty hashbulkdelete hashvacuumcleanup hashcostestimate hashoptions ));
+DATA(insert OID = 405 ( hash 1 1 f f t f f f f f f f 23 hashinsert hashbeginscan hashgettuple hashgetbitmap hashrescan hashendscan hashmarkpos hashrestrpos hashbuild hashbuildempty hashbulkdelete hashvacuumcleanup hashcostestimate hashoptions ));
DESCR("hash index access method");
#define HASH_AM_OID 405
-DATA(insert OID = 783 ( gist 0 8 f t f f t t t t t 0 gistinsert gistbeginscan gistgettuple gistgetbitmap gistrescan gistendscan gistmarkpos gistrestrpos gistbuild gistbuildempty gistbulkdelete gistvacuumcleanup gistcostestimate gistoptions ));
+DATA(insert OID = 783 ( gist 0 8 f t f f t t t t t f 0 gistinsert gistbeginscan gistgettuple gistgetbitmap gistrescan gistendscan gistmarkpos gistrestrpos gistbuild gistbuildempty gistbulkdelete gistvacuumcleanup gistcostestimate gistoptions ));
DESCR("GiST index access method");
#define GIST_AM_OID 783
-DATA(insert OID = 2742 ( gin 0 5 f f f f t t f t f 0 gininsert ginbeginscan - gingetbitmap ginrescan ginendscan ginmarkpos ginrestrpos ginbuild ginbuildempty ginbulkdelete ginvacuumcleanup gincostestimate ginoptions ));
+DATA(insert OID = 2742 ( gin 0 5 f f f f t t f t f f 0 gininsert ginbeginscan - gingetbitmap ginrescan ginendscan ginmarkpos ginrestrpos ginbuild ginbuildempty ginbulkdelete ginvacuumcleanup gincostestimate ginoptions ));
DESCR("GIN index access method");
#define GIN_AM_OID 2742
diff --git a/src/include/commands/variable.h b/src/include/commands/variable.h
index 2fc144e1e12..39bccbd5bf6 100644
--- a/src/include/commands/variable.h
+++ b/src/include/commands/variable.h
@@ -26,6 +26,8 @@ extern bool assign_transaction_read_only(bool value,
extern const char *assign_XactIsoLevel(const char *value,
bool doit, GucSource source);
extern const char *show_XactIsoLevel(void);
+extern bool assign_transaction_deferrable(bool newval, bool doit,
+ GucSource source);
extern bool assign_random_seed(double value,
bool doit, GucSource source);
extern const char *show_random_seed(void);
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index f5e267c9882..99fe37ef83b 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -27,6 +27,10 @@
#define LOG2_NUM_LOCK_PARTITIONS 4
#define NUM_LOCK_PARTITIONS (1 << LOG2_NUM_LOCK_PARTITIONS)
+/* Number of partitions the shared predicate lock tables are divided into */
+#define LOG2_NUM_PREDICATELOCK_PARTITIONS 4
+#define NUM_PREDICATELOCK_PARTITIONS (1 << LOG2_NUM_PREDICATELOCK_PARTITIONS)
+
/*
* We have a number of predefined LWLocks, plus a bunch of LWLocks that are
* dynamically assigned (e.g., for shared buffers). The LWLock structures
@@ -70,12 +74,18 @@ typedef enum LWLockId
RelationMappingLock,
AsyncCtlLock,
AsyncQueueLock,
+ SerializableXactHashLock,
+ SerializableFinishedListLock,
+ SerializablePredicateLockListLock,
+ OldSerXidLock,
+ PredicateLockNextRowLinkLock,
/* Individual lock IDs end here */
FirstBufMappingLock,
FirstLockMgrLock = FirstBufMappingLock + NUM_BUFFER_PARTITIONS,
+ FirstPredicateLockMgrLock = FirstLockMgrLock + NUM_LOCK_PARTITIONS,
/* must be last except for MaxDynamicLWLock: */
- NumFixedLWLocks = FirstLockMgrLock + NUM_LOCK_PARTITIONS,
+ NumFixedLWLocks = FirstPredicateLockMgrLock + NUM_PREDICATELOCK_PARTITIONS,
MaxDynamicLWLock = 1000000000
} LWLockId;
diff --git a/src/include/storage/predicate.h b/src/include/storage/predicate.h
new file mode 100644
index 00000000000..163d8cb3ff5
--- /dev/null
+++ b/src/include/storage/predicate.h
@@ -0,0 +1,67 @@
+/*-------------------------------------------------------------------------
+ *
+ * predicate.h
+ * POSTGRES public predicate locking definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/predicate.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PREDICATE_H
+#define PREDICATE_H
+
+#include "utils/relcache.h"
+#include "utils/snapshot.h"
+
+
+/*
+ * GUC variables
+ */
+extern int max_predicate_locks_per_xact;
+
+
+/* Number of SLRU buffers to use for predicate locking */
+#define NUM_OLDSERXID_BUFFERS 16
+
+
+/*
+ * function prototypes
+ */
+
+/* housekeeping for shared memory predicate lock structures */
+extern void InitPredicateLocks(void);
+extern Size PredicateLockShmemSize(void);
+
+/* predicate lock reporting */
+extern bool PageIsPredicateLocked(const Relation relation, const BlockNumber blkno);
+
+/* predicate lock maintenance */
+extern Snapshot RegisterSerializableTransaction(Snapshot snapshot);
+extern void RegisterPredicateLockingXid(const TransactionId xid);
+extern void PredicateLockRelation(const Relation relation);
+extern void PredicateLockPage(const Relation relation, const BlockNumber blkno);
+extern void PredicateLockTuple(const Relation relation, const HeapTuple tuple);
+extern void PredicateLockTupleRowVersionLink(const Relation relation, const HeapTuple oldTuple, const HeapTuple newTuple);
+extern void PredicateLockPageSplit(const Relation relation, const BlockNumber oldblkno, const BlockNumber newblkno);
+extern void PredicateLockPageCombine(const Relation relation, const BlockNumber oldblkno, const BlockNumber newblkno);
+extern void ReleasePredicateLocks(const bool isCommit);
+
+/* conflict detection (may also trigger rollback) */
+extern void CheckForSerializableConflictOut(const bool valid, const Relation relation, const HeapTuple tuple, const Buffer buffer);
+extern void CheckForSerializableConflictIn(const Relation relation, const HeapTuple tuple, const Buffer buffer);
+
+/* final rollback checking */
+extern void PreCommit_CheckForSerializationFailure(void);
+
+/* two-phase commit support */
+extern void AtPrepare_PredicateLocks(void);
+extern void PostPrepare_PredicateLocks(TransactionId xid);
+extern void PredicateLockTwoPhaseFinish(TransactionId xid, bool isCommit);
+extern void predicatelock_twophase_recover(TransactionId xid, uint16 info,
+ void *recdata, uint32 len);
+
+#endif /* PREDICATE_H */
diff --git a/src/include/storage/predicate_internals.h b/src/include/storage/predicate_internals.h
new file mode 100644
index 00000000000..41aa70fdfa1
--- /dev/null
+++ b/src/include/storage/predicate_internals.h
@@ -0,0 +1,476 @@
+/*-------------------------------------------------------------------------
+ *
+ * predicate_internals.h
+ * POSTGRES internal predicate locking definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/predicate_internals.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PREDICATE_INTERNALS_H
+#define PREDICATE_INTERNALS_H
+
+#include "storage/lock.h"
+
+/*
+ * Commit number.
+ */
+typedef uint64 SerCommitSeqNo;
+
+/*
+ * Reserved commit sequence numbers:
+ * - 0 is reserved to indicate a non-existent SLRU entry; it cannot be
+ * used as a SerCommitSeqNo, even an invalid one
+ * - InvalidSerCommitSeqNo is used to indicate a transaction that
+ * hasn't committed yet, so use a number greater than all valid
+ * ones to make comparison do the expected thing
+ * - RecoverySerCommitSeqNo is used to refer to transactions that
+ * happened before a crash/recovery, since we restart the sequence
+ * at that point. It's earlier than all normal sequence numbers,
+ * and is only used by recovered prepared transactions
+ */
+#define InvalidSerCommitSeqNo UINT64_MAX
+#define RecoverySerCommitSeqNo ((SerCommitSeqNo) 1)
+#define FirstNormalSerCommitSeqNo ((SerCommitSeqNo) 2)
+
+/*
+ * The SERIALIZABLEXACT struct contains information needed for each
+ * serializable database transaction to support SSI techniques.
+ *
+ * A home-grown list is maintained in shared memory to manage these.
+ * An entry is used when the serializable transaction acquires a snapshot.
+ * Unless the transaction is rolled back, this entry must generally remain
+ * until all concurrent transactions have completed. (There are special
+ * optimizations for READ ONLY transactions which often allow them to be
+ * cleaned up earlier.) A transaction which is rolled back is cleaned up
+ * as soon as possible.
+ *
+ * Eligibility for cleanup of committed transactions is generally determined
+ * by comparing the transaction's finishedBefore field to
+ * SerializableGlobalXmin.
+ */
+typedef struct SERIALIZABLEXACT
+{
+ VirtualTransactionId vxid; /* The executing process always has one of
+ * these. */
+ SerCommitSeqNo commitSeqNo;
+ union /* these values are not both interesting at
+ * the same time */
+ {
+ SerCommitSeqNo earliestOutConflictCommit; /* when committed with
+ * conflict out */
+ SerCommitSeqNo lastCommitBeforeSnapshot; /* when not committed or
+ * no conflict out */
+ } SeqNo;
+ SHM_QUEUE outConflicts; /* list of write transactions whose data we
+ * couldn't read. */
+ SHM_QUEUE inConflicts; /* list of read transactions which couldn't
+ * see our write. */
+ SHM_QUEUE predicateLocks; /* list of associated PREDICATELOCK objects */
+ SHM_QUEUE finishedLink; /* list link in
+ * FinishedSerializableTransactions */
+
+ /*
+ * for r/o transactions: list of concurrent r/w transactions that we could
+ * potentially have conflicts with, and vice versa for r/w transactions
+ */
+ SHM_QUEUE possibleUnsafeConflicts;
+
+ TransactionId topXid; /* top level xid for the transaction, if one
+ * exists; else invalid */
+ TransactionId finishedBefore; /* invalid means still running; else
+ * the struct expires when no
+ * serializable xids are before this. */
+ TransactionId xmin; /* the transaction's snapshot xmin */
+ uint32 flags; /* OR'd combination of values defined below */
+ int pid; /* pid of associated process */
+} SERIALIZABLEXACT;
+
+#define SXACT_FLAG_ROLLED_BACK 0x00000001
+#define SXACT_FLAG_COMMITTED 0x00000002
+#define SXACT_FLAG_CONFLICT_OUT 0x00000004
+#define SXACT_FLAG_READ_ONLY 0x00000008
+#define SXACT_FLAG_DID_WRITE 0x00000010
+#define SXACT_FLAG_MARKED_FOR_DEATH 0x00000020
+#define SXACT_FLAG_DEFERRABLE_WAITING 0x00000040
+#define SXACT_FLAG_RO_SAFE 0x00000080
+#define SXACT_FLAG_RO_UNSAFE 0x00000100
+#define SXACT_FLAG_SUMMARY_CONFLICT_IN 0x00000200
+#define SXACT_FLAG_SUMMARY_CONFLICT_OUT 0x00000400
+#define SXACT_FLAG_PREPARED 0x00000800
+
+/*
+ * The following types are used to provide an ad hoc list for holding
+ * SERIALIZABLEXACT objects. An HTAB is overkill, since there is no need to
+ * access these by key -- there are direct pointers to these objects where
+ * needed. If a shared memory list is created, these types can probably be
+ * eliminated in favor of using the general solution.
+ */
+typedef struct PredXactListElementData
+{
+ SHM_QUEUE link;
+ SERIALIZABLEXACT sxact;
+} PredXactListElementData;
+
+typedef struct PredXactListElementData *PredXactListElement;
+
+#define PredXactListElementDataSize \
+ ((Size)MAXALIGN(sizeof(PredXactListElementData)))
+
+typedef struct PredXactListData
+{
+ SHM_QUEUE availableList;
+ SHM_QUEUE activeList;
+
+ /*
+ * These global variables are maintained when registering and cleaning up
+ * serializable transactions. They must be global across all backends,
+ * but are not needed outside the predicate.c source file.
+ */
+ TransactionId SxactGlobalXmin; /* global xmin for active serializable
+ * transactions */
+ int SxactGlobalXminCount; /* how many active serializable
+ * transactions have this xmin */
+ int WritableSxactCount; /* how many non-read-only serializable
+ * transactions are active */
+ SerCommitSeqNo LastSxactCommitSeqNo; /* a strictly monotonically
+ * increasing number for
+ * commits of serializable
+ * transactions */
+ /* Protected by SerializableXactHashLock. */
+ SerCommitSeqNo CanPartialClearThrough; /* can clear predicate locks
+ * and inConflicts for
+ * committed transactions
+ * through this seq no */
+ /* Protected by SerializableFinishedListLock. */
+ SerCommitSeqNo HavePartialClearedThrough; /* have cleared through this
+ * seq no */
+ SERIALIZABLEXACT *OldCommittedSxact; /* shared copy of dummy sxact */
+ bool NeedTargetLinkCleanup; /* to save cleanup effort for rare
+ * case */
+
+ PredXactListElement element;
+} PredXactListData;
+
+typedef struct PredXactListData *PredXactList;
+
+#define PredXactListDataSize \
+ ((Size)MAXALIGN(sizeof(PredXactListData)))
+
+
+/*
+ * The following types are used to provide lists of rw-conflicts between
+ * pairs of transactions. Since exactly the same information is needed,
+ * they are also used to record possible unsafe transaction relationships
+ * for purposes of identifying safe snapshots for read-only transactions.
+ *
+ * When a RWConflictData is not in use to record either type of relationship
+ * between a pair of transactions, it is kept on an "available" list. The
+ * outLink field is used for maintaining that list.
+ */
+typedef struct RWConflictData
+{
+ SHM_QUEUE outLink; /* link for list of conflicts out from a sxact */
+ SHM_QUEUE inLink; /* link for list of conflicts in to a sxact */
+ SERIALIZABLEXACT *sxactOut;
+ SERIALIZABLEXACT *sxactIn;
+} RWConflictData;
+
+typedef struct RWConflictData *RWConflict;
+
+#define RWConflictDataSize \
+ ((Size)MAXALIGN(sizeof(RWConflictData)))
+
+typedef struct RWConflictPoolHeaderData
+{
+ SHM_QUEUE availableList;
+ RWConflict element;
+} RWConflictPoolHeaderData;
+
+typedef struct RWConflictPoolHeaderData *RWConflictPoolHeader;
+
+#define RWConflictPoolHeaderDataSize \
+ ((Size)MAXALIGN(sizeof(RWConflictPoolHeaderData)))
+
+
+/*
+ * The SERIALIZABLEXIDTAG struct identifies an xid assigned to a serializable
+ * transaction or any of its subtransactions.
+ */
+typedef struct SERIALIZABLEXIDTAG
+{
+ TransactionId xid;
+} SERIALIZABLEXIDTAG;
+
+/*
+ * The SERIALIZABLEXID struct provides a link from a TransactionId for a
+ * serializable transaction to the related SERIALIZABLEXACT record, even if
+ * the transaction has completed and its connection has been closed.
+ *
+ * These are created as new top level transaction IDs are first assigned to
+ * transactions which are participating in predicate locking. This may
+ * never happen for a particular transaction if it doesn't write anything.
+ * They are removed with their related serializable transaction objects.
+ *
+ * The SubTransGetTopmostTransaction method is used where necessary to get
+ * from an XID which might be from a subtransaction to the top level XID.
+ */
+typedef struct SERIALIZABLEXID
+{
+ /* hash key */
+ SERIALIZABLEXIDTAG tag;
+
+ /* data */
+ SERIALIZABLEXACT *myXact; /* pointer to the top level transaction data */
+} SERIALIZABLEXID;
+
+
+/*
+ * The PREDICATELOCKTARGETTAG struct identifies a database object which can
+ * be the target of predicate locks. It is designed to fit into 16 bytes
+ * with no padding. Note that this would need adjustment if we widen Oid or
+ * BlockNumber to more than 32 bits.
+ *
+ * TODO SSI: If we always use the same fields for the same type of value, we
+ * should rename these. Holding off until it's clear there are no exceptions.
+ * Since indexes are relations with blocks and tuples, it's looking likely that
+ * the rename will be possible. If not, we may need to divide the last field
+ * and use part of it for a target type, so that we know how to interpret the
+ * data..
+ */
+typedef struct PREDICATELOCKTARGETTAG
+{
+ uint32 locktag_field1; /* a 32-bit ID field */
+ uint32 locktag_field2; /* a 32-bit ID field */
+ uint32 locktag_field3; /* a 32-bit ID field */
+ uint16 locktag_field4; /* a 16-bit ID field */
+ uint16 locktag_field5; /* a 16-bit ID field */
+} PREDICATELOCKTARGETTAG;
+
+/*
+ * The PREDICATELOCKTARGET struct represents a database object on which there
+ * are predicate locks.
+ *
+ * A hash list of these objects is maintained in shared memory. An entry is
+ * added when a predicate lock is requested on an object which doesn't
+ * already have one. An entry is removed when the last lock is removed from
+ * its list.
+ *
+ * Because a check for predicate locks on a tuple target should also find
+ * locks on previous versions of the same row, if there are any created by
+ * overlapping transactions, we keep a pointer to the target for the prior
+ * version of the row. We also keep a pointer to the next version of the
+ * row, so that when we no longer have any predicate locks and the back
+ * pointer is clear, we can clean up the prior pointer for the next version.
+ */
+typedef struct PREDICATELOCKTARGET PREDICATELOCKTARGET;
+
+struct PREDICATELOCKTARGET
+{
+ /* hash key */
+ PREDICATELOCKTARGETTAG tag; /* unique identifier of lockable object */
+
+ /* data */
+ SHM_QUEUE predicateLocks; /* list of PREDICATELOCK objects assoc. with
+ * predicate lock target */
+
+ /*
+ * The following two pointers are only used for tuple locks, and are only
+ * consulted for conflict detection and cleanup; not for granularity
+ * promotion.
+ */
+ PREDICATELOCKTARGET *priorVersionOfRow; /* what other locks to check */
+ PREDICATELOCKTARGET *nextVersionOfRow; /* who has pointer here for
+ * more targets */
+};
+
+
+/*
+ * The PREDICATELOCKTAG struct identifies an individual predicate lock.
+ *
+ * It is the combination of predicate lock target (which is a lockable
+ * object) and a serializable transaction which has acquired a lock on that
+ * target.
+ */
+typedef struct PREDICATELOCKTAG
+{
+ PREDICATELOCKTARGET *myTarget;
+ SERIALIZABLEXACT *myXact;
+} PREDICATELOCKTAG;
+
+/*
+ * The PREDICATELOCK struct represents an individual lock.
+ *
+ * An entry can be created here when the related database object is read, or
+ * by promotion of multiple finer-grained targets. All entries related to a
+ * serializable transaction are removed when that serializable transaction is
+ * cleaned up. Entries can also be removed when they are combined into a
+ * single coarser-grained lock entry.
+ */
+typedef struct PREDICATELOCK
+{
+ /* hash key */
+ PREDICATELOCKTAG tag; /* unique identifier of lock */
+
+ /* data */
+ SHM_QUEUE targetLink; /* list link in PREDICATELOCKTARGET's list of
+ * predicate locks */
+ SHM_QUEUE xactLink; /* list link in SERIALIZABLEXACT's list of
+ * predicate locks */
+ SerCommitSeqNo commitSeqNo; /* only used for summarized predicate locks */
+} PREDICATELOCK;
+
+
+/*
+ * The LOCALPREDICATELOCK struct represents a local copy of data which is
+ * also present in the PREDICATELOCK table, organized for fast access without
+ * needing to acquire a LWLock. It is strictly for optimization.
+ *
+ * Each serializable transaction creates its own local hash table to hold a
+ * collection of these. This information is used to determine when a number
+ * of fine-grained locks should be promoted to a single coarser-grained lock.
+ * The information is maintained more-or-less in parallel to the
+ * PREDICATELOCK data, but because this data is not protected by locks and is
+ * only used in an optimization heuristic, it is allowed to drift in a few
+ * corner cases where maintaining exact data would be expensive.
+ *
+ * The hash table is created when the serializable transaction acquires its
+ * snapshot, and its memory is released upon completion of the transaction.
+ */
+typedef struct LOCALPREDICATELOCK
+{
+ /* hash key */
+ PREDICATELOCKTARGETTAG tag; /* unique identifier of lockable object */
+
+ /* data */
+ bool held; /* is lock held, or just its children? */
+ int childLocks; /* number of child locks currently held */
+} LOCALPREDICATELOCK;
+
+
+/*
+ * The types of predicate locks which can be acquired.
+ */
+typedef enum PredicateLockTargetType
+{
+ PREDLOCKTAG_RELATION,
+ PREDLOCKTAG_PAGE,
+ PREDLOCKTAG_TUPLE
+ /* TODO SSI: Other types may be needed for index locking */
+} PredicateLockTargetType;
+
+
+/*
+ * This structure is used to quickly capture a copy of all predicate
+ * locks. This is currently used only by the pg_lock_status function,
+ * which in turn is used by the pg_locks view.
+ */
+typedef struct PredicateLockData
+{
+ int nelements;
+ PREDICATELOCKTARGETTAG *locktags;
+ SERIALIZABLEXACT *xacts;
+} PredicateLockData;
+
+
+/*
+ * These macros define how we map logical IDs of lockable objects into the
+ * physical fields of PREDICATELOCKTARGETTAG. Use these to set up values,
+ * rather than accessing the fields directly. Note multiple eval of target!
+ */
+#define SET_PREDICATELOCKTARGETTAG_RELATION(locktag,dboid,reloid) \
+ ((locktag).locktag_field1 = (dboid), \
+ (locktag).locktag_field2 = (reloid), \
+ (locktag).locktag_field3 = InvalidBlockNumber, \
+ (locktag).locktag_field4 = InvalidOffsetNumber, \
+ (locktag).locktag_field5 = 0)
+
+#define SET_PREDICATELOCKTARGETTAG_PAGE(locktag,dboid,reloid,blocknum) \
+ ((locktag).locktag_field1 = (dboid), \
+ (locktag).locktag_field2 = (reloid), \
+ (locktag).locktag_field3 = (blocknum), \
+ (locktag).locktag_field4 = InvalidOffsetNumber, \
+ (locktag).locktag_field5 = 0)
+
+#define SET_PREDICATELOCKTARGETTAG_TUPLE(locktag,dboid,reloid,blocknum,offnum) \
+ ((locktag).locktag_field1 = (dboid), \
+ (locktag).locktag_field2 = (reloid), \
+ (locktag).locktag_field3 = (blocknum), \
+ (locktag).locktag_field4 = (offnum), \
+ (locktag).locktag_field5 = 0)
+
+#define GET_PREDICATELOCKTARGETTAG_DB(locktag) \
+ ((locktag).locktag_field1)
+#define GET_PREDICATELOCKTARGETTAG_RELATION(locktag) \
+ ((locktag).locktag_field2)
+#define GET_PREDICATELOCKTARGETTAG_PAGE(locktag) \
+ ((locktag).locktag_field3)
+#define GET_PREDICATELOCKTARGETTAG_OFFSET(locktag) \
+ ((locktag).locktag_field4)
+#define GET_PREDICATELOCKTARGETTAG_TYPE(locktag) \
+ (((locktag).locktag_field4 != InvalidOffsetNumber) ? PREDLOCKTAG_TUPLE : \
+ (((locktag).locktag_field3 != InvalidBlockNumber) ? PREDLOCKTAG_PAGE : \
+ PREDLOCKTAG_RELATION))
+
+/*
+ * Two-phase commit statefile records. There are two types: for each
+ * transaction, we generate one per-transaction record and a variable
+ * number of per-predicate-lock records.
+ */
+typedef enum TwoPhasePredicateRecordType
+{
+ TWOPHASEPREDICATERECORD_XACT,
+ TWOPHASEPREDICATERECORD_LOCK
+} TwoPhasePredicateRecordType;
+
+/*
+ * Per-transaction information to reconstruct a SERIALIZABLEXACT. Not
+ * much is needed because most of it not meaningful for a recovered
+ * prepared transaction.
+ *
+ * In particular, we do not record the in and out conflict lists for a
+ * prepared transaction because the associated SERIALIZABLEXACTs will
+ * not be available after recovery. Instead, we simply record the
+ * existence of each type of conflict by setting the transaction's
+ * summary conflict in/out flag.
+ */
+typedef struct TwoPhasePredicateXactRecord
+{
+ TransactionId xmin;
+ uint32 flags;
+} TwoPhasePredicateXactRecord;
+
+/* Per-lock state */
+typedef struct TwoPhasePredicateLockRecord
+{
+ PREDICATELOCKTARGETTAG target;
+} TwoPhasePredicateLockRecord;
+
+typedef struct TwoPhasePredicateRecord
+{
+ TwoPhasePredicateRecordType type;
+ union
+ {
+ TwoPhasePredicateXactRecord xactRecord;
+ TwoPhasePredicateLockRecord lockRecord;
+ } data;
+} TwoPhasePredicateRecord;
+
+/*
+ * Define a macro to use for an "empty" SERIALIZABLEXACT reference.
+ */
+#define InvalidSerializableXact ((SERIALIZABLEXACT *) NULL)
+
+
+/*
+ * Function definitions for functions needing awareness of predicate
+ * locking internals.
+ */
+extern PredicateLockData *GetPredicateLockStatusData(void);
+
+
+#endif /* PREDICATE_INTERNALS_H */
diff --git a/src/include/storage/shmem.h b/src/include/storage/shmem.h
index 61e3886bd37..f23740c9e3c 100644
--- a/src/include/storage/shmem.h
+++ b/src/include/storage/shmem.h
@@ -35,7 +35,7 @@ typedef struct SHM_QUEUE
extern void InitShmemAccess(void *seghdr);
extern void InitShmemAllocation(void);
extern void *ShmemAlloc(Size size);
-extern bool ShmemAddrIsValid(void *addr);
+extern bool ShmemAddrIsValid(const void *addr);
extern void InitShmemIndex(void);
extern HTAB *ShmemInitHash(const char *name, long init_size, long max_size,
HASHCTL *infoP, int hash_flags);
@@ -67,8 +67,9 @@ extern void SHMQueueInit(SHM_QUEUE *queue);
extern void SHMQueueElemInit(SHM_QUEUE *queue);
extern void SHMQueueDelete(SHM_QUEUE *queue);
extern void SHMQueueInsertBefore(SHM_QUEUE *queue, SHM_QUEUE *elem);
-extern Pointer SHMQueueNext(SHM_QUEUE *queue, SHM_QUEUE *curElem,
+extern Pointer SHMQueueNext(const SHM_QUEUE *queue, const SHM_QUEUE *curElem,
Size linkOffset);
-extern bool SHMQueueEmpty(SHM_QUEUE *queue);
+extern bool SHMQueueEmpty(const SHM_QUEUE *queue);
+extern bool SHMQueueIsDetached(const SHM_QUEUE *queue);
#endif /* SHMEM_H */
diff --git a/src/test/isolation/.gitignore b/src/test/isolation/.gitignore
new file mode 100644
index 00000000000..42ee9457445
--- /dev/null
+++ b/src/test/isolation/.gitignore
@@ -0,0 +1,12 @@
+# Local binaries
+/isolationtester
+/pg_isolation_regress
+
+# Local generated source files
+/specparse.c
+/specscanner.c
+
+# Generated subdirectories
+/results/
+/log/
+/tmp_check/
diff --git a/src/test/isolation/Makefile b/src/test/isolation/Makefile
new file mode 100644
index 00000000000..1d4c7db8bc4
--- /dev/null
+++ b/src/test/isolation/Makefile
@@ -0,0 +1,74 @@
+#
+# Makefile for isolation tests
+#
+
+subdir = src/test/isolation
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+ifeq ($(PORTNAME), win32)
+LDLIBS += -lws2_32
+endif
+
+override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
+override LDLIBS := $(libpq_pgport) $(LDLIBS)
+
+OBJS = specparse.o isolationtester.o
+
+submake-regress:
+ $(MAKE) -C $(top_builddir)/src/test/regress pg_regress.o
+
+pg_regress.o: | submake-regress
+ rm -f $@ && $(LN_S) $(top_builddir)/src/test/regress/pg_regress.o .
+
+pg_isolation_regress: isolation_main.o pg_regress.o
+ $(CC) $(CFLAGS) $^ $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
+
+all: isolationtester pg_isolation_regress
+
+isolationtester: $(OBJS) | submake-libpq submake-libpgport
+ $(CC) $(CFLAGS) $(OBJS) $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
+
+distprep: specparse.c
+
+# There is no correct way to write a rule that generates two files.
+# Rules with two targets don't have that meaning, they are merely
+# shorthand for two otherwise separate rules. To be safe for parallel
+# make, we must chain the dependencies like this. The semicolon is
+# important, otherwise make will choose the built-in rule for
+# gram.y=>gram.c.
+
+all: isolationtester$(X) pg_isolation_regress$(X)
+
+specparse.h: specparse.c ;
+
+# specscanner is compiled as part of specparse
+specparse.o: specscanner.c
+
+specparse.c: specparse.y
+ifdef BISON
+ $(BISON) $(BISONFLAGS) -o $@ $<
+else
+ @$(missing) bison $< $@
+endif
+
+specscanner.c: specscanner.l
+ifdef FLEX
+ $(FLEX) $(FLEXFLAGS) -o'$@' $<
+else
+ @$(missing) flex $< $@
+endif
+# specparse.c is in the distribution tarball, so is not cleaned here
+clean distclean:
+ rm -f isolationtester$(X) pg_isolation_regress$(X) $(OBJS) isolation_main.o
+ rm -f pg_regress.o
+ rm -rf results
+
+maintainer-clean: distclean
+ rm -f specparse.c specscanner.c
+
+installcheck: all
+ ./pg_isolation_regress --schedule=$(srcdir)/isolation_schedule
+
+check: all
+ ./pg_isolation_regress --temp-install=./tmp_check --top-builddir=$(top_builddir) --schedule=$(srcdir)/isolation_schedule
diff --git a/src/test/isolation/README b/src/test/isolation/README
new file mode 100644
index 00000000000..f6984b0beec
--- /dev/null
+++ b/src/test/isolation/README
@@ -0,0 +1,65 @@
+src/test/isolation/README
+
+Isolation tests
+===============
+
+This directory contains a set of tests for the serializable isolation level.
+Testing isolation requires running multiple overlapping transactions, so
+which requires multiple concurrent connections, and can't therefore be
+tested using the normal pg_regress program.
+
+To represent a test with overlapping transactions, we use a test specification
+file with a custom syntax, described in the next section.
+
+isolationtester is program that uses libpq to open multiple connections,
+and executes a test specified by a spec file. A libpq connection string
+to specify the server and database to connect to, the defaults derived from
+environment variables are used otherwise.
+
+pg_isolation_regress is a tool identical to pg_regress, but instead of using
+psql to execute a test, it uses isolationtester.
+
+To run the tests, you need to have a server up and running. Run
+ gmake installcheck
+
+Test specification
+==================
+
+Each isolation test is defined by a specification file, stored in the specs
+subdirectory. A test specification consists of five parts, in this order:
+
+setup { <SQL> }
+
+ The given SQL block is executed once, in one session only, before running
+ the test. Create any test tables or such objects here. This part is
+ optional.
+
+teardown { <SQL> }
+
+ The teardown SQL block is executed once after the test is finished. Use
+ this to clean up, e.g dropping any test tables. This part is optional.
+
+session "<name>"
+
+ Each session is executed in a separate connection. A session consists
+ of four parts: setup, teardown and one or more steps. The per-session
+ setup and teardown parts have the same syntax as the per-test setup and
+ teardown described above, but they are executed in every session,
+ before and after each permutation. The setup part typically contains a
+ "BEGIN" command to begin a transaction.
+
+ Each step has a syntax of
+
+ step "<name>" { <SQL> }
+
+ where <name> is a unique name identifying this step, and SQL is a SQL
+ statement (or statements, separated by semicolons) that is executed in the
+ step.
+
+permutation "<step name>" ...
+
+ A permutation line specifies a list of steps that are ran in that order.
+ If no permutation lines are given, the test program automatically generates
+ all possible overlapping orderings of the given sessions.
+
+Lines beginning with a # are considered comments.
diff --git a/src/test/isolation/expected/classroom-scheduling.out b/src/test/isolation/expected/classroom-scheduling.out
new file mode 100644
index 00000000000..faae14f45a7
--- /dev/null
+++ b/src/test/isolation/expected/classroom-scheduling.out
@@ -0,0 +1,299 @@
+Parsed test spec with 2 sessions
+
+starting permutation: rx1 wy1 c1 ry2 wx2 c2
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+
+0
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step c1: COMMIT;
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+
+1
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step c2: COMMIT;
+
+starting permutation: rx1 wy1 ry2 c1 wx2 c2
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+
+0
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+
+0
+step c1: COMMIT;
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx1 wy1 ry2 wx2 c1 c2
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+
+0
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+
+0
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 wy1 ry2 wx2 c2 c1
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+
+0
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+
+0
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wy1 c1 wx2 c2
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+
+0
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+
+0
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step c1: COMMIT;
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx1 ry2 wy1 wx2 c1 c2
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+
+0
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+
+0
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wy1 wx2 c2 c1
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+
+0
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+
+0
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wx2 wy1 c1 c2
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+
+0
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+
+0
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wx2 wy1 c2 c1
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+
+0
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+
+0
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wx2 c2 wy1 c1
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+
+0
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+
+0
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step c2: COMMIT;
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: ry2 rx1 wy1 c1 wx2 c2
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+
+0
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+
+0
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step c1: COMMIT;
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: ry2 rx1 wy1 wx2 c1 c2
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+
+0
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+
+0
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 rx1 wy1 wx2 c2 c1
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+
+0
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+
+0
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 rx1 wx2 wy1 c1 c2
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+
+0
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+
+0
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 rx1 wx2 wy1 c2 c1
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+
+0
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+
+0
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 rx1 wx2 c2 wy1 c1
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+
+0
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+
+0
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step c2: COMMIT;
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: ry2 wx2 rx1 wy1 c1 c2
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+
+0
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+
+0
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 wx2 rx1 wy1 c2 c1
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+
+0
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+
+0
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 wx2 rx1 c2 wy1 c1
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+
+0
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+
+0
+step c2: COMMIT;
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: ry2 wx2 c2 rx1 wy1 c1
+step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30';
+count
+
+0
+step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00';
+step c2: COMMIT;
+step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00';
+count
+
+1
+step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol');
+step c1: COMMIT;
diff --git a/src/test/isolation/expected/multiple-row-versions.out b/src/test/isolation/expected/multiple-row-versions.out
new file mode 100644
index 00000000000..cd31029d17b
--- /dev/null
+++ b/src/test/isolation/expected/multiple-row-versions.out
@@ -0,0 +1,24 @@
+Parsed test spec with 4 sessions
+
+starting permutation: rx1 wx2 c2 wx3 ry3 wy4 rz4 c4 c3 wz1 c1
+step rx1: SELECT * FROM t WHERE id = 1000000;
+id txt
+
+1000000
+step wx2: UPDATE t SET txt = 'b' WHERE id = 1000000;
+step c2: COMMIT;
+step wx3: UPDATE t SET txt = 'c' WHERE id = 1000000;
+step ry3: SELECT * FROM t WHERE id = 500000;
+id txt
+
+500000
+step wy4: UPDATE t SET txt = 'd' WHERE id = 500000;
+step rz4: SELECT * FROM t WHERE id = 1;
+id txt
+
+1
+step c4: COMMIT;
+step c3: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step wz1: UPDATE t SET txt = 'a' WHERE id = 1;
+step c1: COMMIT;
diff --git a/src/test/isolation/expected/partial-index.out b/src/test/isolation/expected/partial-index.out
new file mode 100644
index 00000000000..1230513675e
--- /dev/null
+++ b/src/test/isolation/expected/partial-index.out
@@ -0,0 +1,641 @@
+Parsed test spec with 2 sessions
+
+starting permutation: rxy1 wx1 c1 wy2 rxy2 c2
+step rxy1: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+9 a 1
+10 a 1
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step c1: COMMIT;
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy2: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+step c2: COMMIT;
+
+starting permutation: rxy1 wx1 wy2 c1 rxy2 c2
+step rxy1: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+9 a 1
+10 a 1
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step c1: COMMIT;
+step rxy2: select * from test_t where val2 = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rxy1 wx1 wy2 rxy2 c1 c2
+step rxy1: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+9 a 1
+10 a 1
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy2: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+10 a 1
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy1 wx1 wy2 rxy2 c2 c1
+step rxy1: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+9 a 1
+10 a 1
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy2: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+10 a 1
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy1 wy2 wx1 c1 rxy2 c2
+step rxy1: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+9 a 1
+10 a 1
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step c1: COMMIT;
+step rxy2: select * from test_t where val2 = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rxy1 wy2 wx1 rxy2 c1 c2
+step rxy1: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+9 a 1
+10 a 1
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step rxy2: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+10 a 1
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy1 wy2 wx1 rxy2 c2 c1
+step rxy1: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+9 a 1
+10 a 1
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step rxy2: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+10 a 1
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy1 wy2 rxy2 wx1 c1 c2
+step rxy1: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+9 a 1
+10 a 1
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy2: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+10 a 1
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy1 wy2 rxy2 wx1 c2 c1
+step rxy1: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+9 a 1
+10 a 1
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy2: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+10 a 1
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxy1 wy2 rxy2 c2 wx1 c1
+step rxy1: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+9 a 1
+10 a 1
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy2: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+10 a 1
+step c2: COMMIT;
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: wy2 rxy1 wx1 c1 rxy2 c2
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy1: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+9 a 1
+10 a 1
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step c1: COMMIT;
+step rxy2: select * from test_t where val2 = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: wy2 rxy1 wx1 rxy2 c1 c2
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy1: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+9 a 1
+10 a 1
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step rxy2: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+10 a 1
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wy2 rxy1 wx1 rxy2 c2 c1
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy1: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+9 a 1
+10 a 1
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step rxy2: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+10 a 1
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wy2 rxy1 rxy2 wx1 c1 c2
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy1: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+9 a 1
+10 a 1
+step rxy2: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+10 a 1
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wy2 rxy1 rxy2 wx1 c2 c1
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy1: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+9 a 1
+10 a 1
+step rxy2: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+10 a 1
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wy2 rxy1 rxy2 c2 wx1 c1
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy1: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+9 a 1
+10 a 1
+step rxy2: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+10 a 1
+step c2: COMMIT;
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: wy2 rxy2 rxy1 wx1 c1 c2
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy2: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+10 a 1
+step rxy1: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+9 a 1
+10 a 1
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wy2 rxy2 rxy1 wx1 c2 c1
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy2: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+10 a 1
+step rxy1: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+9 a 1
+10 a 1
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wy2 rxy2 rxy1 c2 wx1 c1
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy2: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+10 a 1
+step rxy1: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+9 a 1
+10 a 1
+step c2: COMMIT;
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: wy2 rxy2 c2 rxy1 wx1 c1
+step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9;
+step rxy2: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+10 a 1
+step c2: COMMIT;
+step rxy1: select * from test_t where val2 = 1;
+id val1 val2
+
+0 a 1
+1 a 1
+2 a 1
+3 a 1
+4 a 1
+5 a 1
+6 a 1
+7 a 1
+8 a 1
+10 a 1
+step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10;
+step c1: COMMIT;
diff --git a/src/test/isolation/expected/project-manager.out b/src/test/isolation/expected/project-manager.out
new file mode 100644
index 00000000000..0050a744a86
--- /dev/null
+++ b/src/test/isolation/expected/project-manager.out
@@ -0,0 +1,299 @@
+Parsed test spec with 2 sessions
+
+starting permutation: rx1 wy1 c1 ry2 wx2 c2
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+
+1
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step c1: COMMIT;
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+
+1
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step c2: COMMIT;
+
+starting permutation: rx1 wy1 ry2 c1 wx2 c2
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+
+1
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+
+0
+step c1: COMMIT;
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx1 wy1 ry2 wx2 c1 c2
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+
+1
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+
+0
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 wy1 ry2 wx2 c2 c1
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+
+1
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+
+0
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wy1 c1 wx2 c2
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+
+1
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+
+0
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step c1: COMMIT;
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx1 ry2 wy1 wx2 c1 c2
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+
+1
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+
+0
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wy1 wx2 c2 c1
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+
+1
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+
+0
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wx2 wy1 c1 c2
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+
+1
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+
+0
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wx2 wy1 c2 c1
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+
+1
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+
+0
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wx2 c2 wy1 c1
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+
+1
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+
+0
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step c2: COMMIT;
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: ry2 rx1 wy1 c1 wx2 c2
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+
+0
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+
+1
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step c1: COMMIT;
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: ry2 rx1 wy1 wx2 c1 c2
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+
+0
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+
+1
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 rx1 wy1 wx2 c2 c1
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+
+0
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+
+1
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 rx1 wx2 wy1 c1 c2
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+
+0
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+
+1
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 rx1 wx2 wy1 c2 c1
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+
+0
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+
+1
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 rx1 wx2 c2 wy1 c1
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+
+0
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+
+1
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step c2: COMMIT;
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: ry2 wx2 rx1 wy1 c1 c2
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+
+0
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+
+1
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 wx2 rx1 wy1 c2 c1
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+
+0
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+
+1
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 wx2 rx1 c2 wy1 c1
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+
+0
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+
+1
+step c2: COMMIT;
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: ry2 wx2 c2 rx1 wy1 c1
+step ry2: SELECT count(*) FROM project WHERE project_manager = 1;
+count
+
+0
+step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1;
+step c2: COMMIT;
+step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager;
+count
+
+0
+step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1);
+step c1: COMMIT;
diff --git a/src/test/isolation/expected/receipt-report.out b/src/test/isolation/expected/receipt-report.out
new file mode 100644
index 00000000000..bcab10ea959
--- /dev/null
+++ b/src/test/isolation/expected/receipt-report.out
@@ -0,0 +1,3379 @@
+Parsed test spec with 3 sessions
+
+starting permutation: rxwy1 c1 wx2 c2 rx3 ry3 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-23-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+3 12-22-2008 4.00
+step c3: COMMIT;
+
+starting permutation: rxwy1 c1 wx2 rx3 c2 ry3 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+3 12-22-2008 4.00
+step c3: COMMIT;
+
+starting permutation: rxwy1 c1 wx2 rx3 ry3 c2 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+3 12-22-2008 4.00
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 c1 wx2 rx3 ry3 c3 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+3 12-22-2008 4.00
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 c1 rx3 wx2 c2 ry3 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+3 12-22-2008 4.00
+step c3: COMMIT;
+
+starting permutation: rxwy1 c1 rx3 wx2 ry3 c2 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+3 12-22-2008 4.00
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 c1 rx3 wx2 ry3 c3 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+3 12-22-2008 4.00
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 c1 rx3 ry3 wx2 c2 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+3 12-22-2008 4.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 c1 rx3 ry3 wx2 c3 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+3 12-22-2008 4.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 c1 rx3 ry3 c3 wx2 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+3 12-22-2008 4.00
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+
+starting permutation: rxwy1 wx2 c1 c2 rx3 ry3 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-23-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+3 12-22-2008 4.00
+step c3: COMMIT;
+
+starting permutation: rxwy1 wx2 c1 rx3 c2 ry3 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+3 12-22-2008 4.00
+step c3: COMMIT;
+
+starting permutation: rxwy1 wx2 c1 rx3 ry3 c2 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+3 12-22-2008 4.00
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 wx2 c1 rx3 ry3 c3 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+3 12-22-2008 4.00
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 wx2 c2 c1 rx3 ry3 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-23-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+3 12-22-2008 4.00
+step c3: COMMIT;
+
+starting permutation: rxwy1 wx2 c2 rx3 c1 ry3 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-23-2008
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT;
+
+starting permutation: rxwy1 wx2 c2 rx3 ry3 c1 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-23-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT;
+
+starting permutation: rxwy1 wx2 c2 rx3 ry3 c3 c1
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-23-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxwy1 wx2 rx3 c1 c2 ry3 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c1: COMMIT;
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+
+starting permutation: rxwy1 wx2 rx3 c1 ry3 c2 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 wx2 rx3 c1 ry3 c3 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 wx2 rx3 c2 c1 ry3 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c2: COMMIT;
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+
+starting permutation: rxwy1 wx2 rx3 c2 ry3 c1 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 wx2 rx3 c2 ry3 c3 c1
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy1 wx2 rx3 ry3 c1 c2 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 wx2 rx3 ry3 c1 c3 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 wx2 rx3 ry3 c2 c1 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 wx2 rx3 ry3 c2 c3 c1
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy1 wx2 rx3 ry3 c3 c1 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 wx2 rx3 ry3 c3 c2 c1
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy1 rx3 c1 wx2 c2 ry3 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+
+starting permutation: rxwy1 rx3 c1 wx2 ry3 c2 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 rx3 c1 wx2 ry3 c3 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 rx3 c1 ry3 wx2 c2 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 rx3 c1 ry3 wx2 c3 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 rx3 c1 ry3 c3 wx2 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+
+starting permutation: rxwy1 rx3 wx2 c1 c2 ry3 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+
+starting permutation: rxwy1 rx3 wx2 c1 ry3 c2 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 rx3 wx2 c1 ry3 c3 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 rx3 wx2 c2 c1 ry3 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+
+starting permutation: rxwy1 rx3 wx2 c2 ry3 c1 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 rx3 wx2 c2 ry3 c3 c1
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy1 rx3 wx2 ry3 c1 c2 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 rx3 wx2 ry3 c1 c3 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 rx3 wx2 ry3 c2 c1 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 rx3 wx2 ry3 c2 c3 c1
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy1 rx3 wx2 ry3 c3 c1 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 rx3 wx2 ry3 c3 c2 c1
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy1 rx3 ry3 c1 wx2 c2 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 rx3 ry3 c1 wx2 c3 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 rx3 ry3 c1 c3 wx2 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+
+starting permutation: rxwy1 rx3 ry3 wx2 c1 c2 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 rx3 ry3 wx2 c1 c3 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 rx3 ry3 wx2 c2 c1 c3
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy1 rx3 ry3 wx2 c2 c3 c1
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy1 rx3 ry3 wx2 c3 c1 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 rx3 ry3 wx2 c3 c2 c1
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy1 rx3 ry3 c3 c1 wx2 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+
+starting permutation: rxwy1 rx3 ry3 c3 wx2 c1 c2
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy1 rx3 ry3 c3 wx2 c2 c1
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 rxwy1 c1 c2 rx3 ry3 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-23-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+3 12-22-2008 4.00
+step c3: COMMIT;
+
+starting permutation: wx2 rxwy1 c1 rx3 c2 ry3 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+3 12-22-2008 4.00
+step c3: COMMIT;
+
+starting permutation: wx2 rxwy1 c1 rx3 ry3 c2 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+3 12-22-2008 4.00
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rxwy1 c1 rx3 ry3 c3 c2
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+3 12-22-2008 4.00
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx2 rxwy1 c2 c1 rx3 ry3 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-23-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+3 12-22-2008 4.00
+step c3: COMMIT;
+
+starting permutation: wx2 rxwy1 c2 rx3 c1 ry3 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-23-2008
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT;
+
+starting permutation: wx2 rxwy1 c2 rx3 ry3 c1 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-23-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT;
+
+starting permutation: wx2 rxwy1 c2 rx3 ry3 c3 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-23-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wx2 rxwy1 rx3 c1 c2 ry3 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c1: COMMIT;
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+
+starting permutation: wx2 rxwy1 rx3 c1 ry3 c2 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rxwy1 rx3 c1 ry3 c3 c2
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx2 rxwy1 rx3 c2 c1 ry3 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c2: COMMIT;
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+
+starting permutation: wx2 rxwy1 rx3 c2 ry3 c1 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rxwy1 rx3 c2 ry3 c3 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 rxwy1 rx3 ry3 c1 c2 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rxwy1 rx3 ry3 c1 c3 c2
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx2 rxwy1 rx3 ry3 c2 c1 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rxwy1 rx3 ry3 c2 c3 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 rxwy1 rx3 ry3 c3 c1 c2
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx2 rxwy1 rx3 ry3 c3 c2 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 c2 rxwy1 c1 rx3 ry3 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-23-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+
+starting permutation: wx2 c2 rxwy1 rx3 c1 ry3 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-23-2008
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+
+starting permutation: wx2 c2 rxwy1 rx3 ry3 c1 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-23-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 c2 rxwy1 rx3 ry3 c3 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-23-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 c2 rx3 rxwy1 c1 ry3 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-23-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+
+starting permutation: wx2 c2 rx3 rxwy1 ry3 c1 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-23-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 c2 rx3 rxwy1 ry3 c3 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-23-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 c2 rx3 ry3 rxwy1 c1 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-23-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 c2 rx3 ry3 rxwy1 c3 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-23-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 c2 rx3 ry3 c3 rxwy1 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-23-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+
+starting permutation: wx2 rx3 rxwy1 c1 c2 ry3 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+
+starting permutation: wx2 rx3 rxwy1 c1 ry3 c2 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rx3 rxwy1 c1 ry3 c3 c2
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx2 rx3 rxwy1 c2 c1 ry3 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+
+starting permutation: wx2 rx3 rxwy1 c2 ry3 c1 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rx3 rxwy1 c2 ry3 c3 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 rx3 rxwy1 ry3 c1 c2 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rx3 rxwy1 ry3 c1 c3 c2
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx2 rx3 rxwy1 ry3 c2 c1 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rx3 rxwy1 ry3 c2 c3 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 rx3 rxwy1 ry3 c3 c1 c2
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx2 rx3 rxwy1 ry3 c3 c2 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 rx3 c2 rxwy1 c1 ry3 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+
+starting permutation: wx2 rx3 c2 rxwy1 ry3 c1 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rx3 c2 rxwy1 ry3 c3 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 rx3 c2 ry3 rxwy1 c1 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rx3 c2 ry3 rxwy1 c3 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 rx3 c2 ry3 c3 rxwy1 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+
+starting permutation: wx2 rx3 ry3 rxwy1 c1 c2 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rx3 ry3 rxwy1 c1 c3 c2
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx2 rx3 ry3 rxwy1 c2 c1 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rx3 ry3 rxwy1 c2 c3 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 rx3 ry3 rxwy1 c3 c1 c2
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx2 rx3 ry3 rxwy1 c3 c2 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 rx3 ry3 c2 rxwy1 c1 c3
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx2 rx3 ry3 c2 rxwy1 c3 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 rx3 ry3 c2 c3 rxwy1 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c2: COMMIT;
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+
+starting permutation: wx2 rx3 ry3 c3 rxwy1 c1 c2
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx2 rx3 ry3 c3 rxwy1 c2 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx2 rx3 ry3 c3 c2 rxwy1 c1
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+
+starting permutation: rx3 rxwy1 c1 wx2 c2 ry3 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+
+starting permutation: rx3 rxwy1 c1 wx2 ry3 c2 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 rxwy1 c1 wx2 ry3 c3 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 rxwy1 c1 ry3 wx2 c2 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 rxwy1 c1 ry3 wx2 c3 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 rxwy1 c1 ry3 c3 wx2 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+
+starting permutation: rx3 rxwy1 wx2 c1 c2 ry3 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+
+starting permutation: rx3 rxwy1 wx2 c1 ry3 c2 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 rxwy1 wx2 c1 ry3 c3 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 rxwy1 wx2 c2 c1 ry3 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+
+starting permutation: rx3 rxwy1 wx2 c2 ry3 c1 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 rxwy1 wx2 c2 ry3 c3 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 rxwy1 wx2 ry3 c1 c2 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 rxwy1 wx2 ry3 c1 c3 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 rxwy1 wx2 ry3 c2 c1 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 rxwy1 wx2 ry3 c2 c3 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 rxwy1 wx2 ry3 c3 c1 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 rxwy1 wx2 ry3 c3 c2 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 rxwy1 ry3 c1 wx2 c2 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 rxwy1 ry3 c1 wx2 c3 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 rxwy1 ry3 c1 c3 wx2 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+
+starting permutation: rx3 rxwy1 ry3 wx2 c1 c2 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 rxwy1 ry3 wx2 c1 c3 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 rxwy1 ry3 wx2 c2 c1 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 rxwy1 ry3 wx2 c2 c3 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 rxwy1 ry3 wx2 c3 c1 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 rxwy1 ry3 wx2 c3 c2 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 rxwy1 ry3 c3 c1 wx2 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+
+starting permutation: rx3 rxwy1 ry3 c3 wx2 c1 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 rxwy1 ry3 c3 wx2 c2 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 wx2 rxwy1 c1 c2 ry3 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+
+starting permutation: rx3 wx2 rxwy1 c1 ry3 c2 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 wx2 rxwy1 c1 ry3 c3 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 wx2 rxwy1 c2 c1 ry3 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+
+starting permutation: rx3 wx2 rxwy1 c2 ry3 c1 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 wx2 rxwy1 c2 ry3 c3 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 wx2 rxwy1 ry3 c1 c2 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 wx2 rxwy1 ry3 c1 c3 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 wx2 rxwy1 ry3 c2 c1 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 wx2 rxwy1 ry3 c2 c3 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 wx2 rxwy1 ry3 c3 c1 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 wx2 rxwy1 ry3 c3 c2 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 wx2 c2 rxwy1 c1 ry3 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+
+starting permutation: rx3 wx2 c2 rxwy1 ry3 c1 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 wx2 c2 rxwy1 ry3 c3 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 wx2 c2 ry3 rxwy1 c1 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 wx2 c2 ry3 rxwy1 c3 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 wx2 c2 ry3 c3 rxwy1 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+
+starting permutation: rx3 wx2 ry3 rxwy1 c1 c2 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 wx2 ry3 rxwy1 c1 c3 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 wx2 ry3 rxwy1 c2 c1 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 wx2 ry3 rxwy1 c2 c3 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 wx2 ry3 rxwy1 c3 c1 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 wx2 ry3 rxwy1 c3 c2 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 wx2 ry3 c2 rxwy1 c1 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 wx2 ry3 c2 rxwy1 c3 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 wx2 ry3 c2 c3 rxwy1 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c2: COMMIT;
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+
+starting permutation: rx3 wx2 ry3 c3 rxwy1 c1 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 wx2 ry3 c3 rxwy1 c2 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 wx2 ry3 c3 c2 rxwy1 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+
+starting permutation: rx3 ry3 rxwy1 c1 wx2 c2 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 ry3 rxwy1 c1 wx2 c3 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 ry3 rxwy1 c1 c3 wx2 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+
+starting permutation: rx3 ry3 rxwy1 wx2 c1 c2 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 ry3 rxwy1 wx2 c1 c3 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 ry3 rxwy1 wx2 c2 c1 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 ry3 rxwy1 wx2 c2 c3 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 ry3 rxwy1 wx2 c3 c1 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 ry3 rxwy1 wx2 c3 c2 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 ry3 rxwy1 c3 c1 wx2 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+
+starting permutation: rx3 ry3 rxwy1 c3 wx2 c1 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 ry3 rxwy1 c3 wx2 c2 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 ry3 wx2 rxwy1 c1 c2 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 ry3 wx2 rxwy1 c1 c3 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 ry3 wx2 rxwy1 c2 c1 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 ry3 wx2 rxwy1 c2 c3 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 ry3 wx2 rxwy1 c3 c1 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 ry3 wx2 rxwy1 c3 c2 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 ry3 wx2 c2 rxwy1 c1 c3
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rx3 ry3 wx2 c2 rxwy1 c3 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 ry3 wx2 c2 c3 rxwy1 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+
+starting permutation: rx3 ry3 wx2 c3 rxwy1 c1 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 ry3 wx2 c3 rxwy1 c2 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 ry3 wx2 c3 c2 rxwy1 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c3: COMMIT;
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+
+starting permutation: rx3 ry3 c3 rxwy1 c1 wx2 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+
+starting permutation: rx3 ry3 c3 rxwy1 wx2 c1 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 ry3 c3 rxwy1 wx2 c2 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 ry3 c3 wx2 rxwy1 c1 c2
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx3 ry3 c3 wx2 rxwy1 c2 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx3 ry3 c3 wx2 c2 rxwy1 c1
+step rx3: SELECT * FROM ctl WHERE k = 'receipt';
+k deposit_date
+
+receipt 12-22-2008
+step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22';
+receipt_no deposit_date amount
+
+1 12-22-2008 1.00
+2 12-22-2008 2.00
+step c3: COMMIT;
+step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt';
+step c2: COMMIT;
+step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00);
+step c1: COMMIT;
diff --git a/src/test/isolation/expected/referential-integrity.out b/src/test/isolation/expected/referential-integrity.out
new file mode 100644
index 00000000000..569d0347cc0
--- /dev/null
+++ b/src/test/isolation/expected/referential-integrity.out
@@ -0,0 +1,629 @@
+Parsed test spec with 2 sessions
+
+starting permutation: rx1 wy1 c1 rx2 ry2 wx2 c2
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step wy1: INSERT INTO b VALUES (1);
+step c1: COMMIT;
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+1
+step wx2: DELETE FROM a WHERE i = 1;
+step c2: COMMIT;
+
+starting permutation: rx1 wy1 rx2 c1 ry2 wx2 c2
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step wy1: INSERT INTO b VALUES (1);
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step c1: COMMIT;
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step wx2: DELETE FROM a WHERE i = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx1 wy1 rx2 ry2 c1 wx2 c2
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step wy1: INSERT INTO b VALUES (1);
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step c1: COMMIT;
+step wx2: DELETE FROM a WHERE i = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx1 wy1 rx2 ry2 wx2 c1 c2
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step wy1: INSERT INTO b VALUES (1);
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step wx2: DELETE FROM a WHERE i = 1;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 wy1 rx2 ry2 wx2 c2 c1
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step wy1: INSERT INTO b VALUES (1);
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step wx2: DELETE FROM a WHERE i = 1;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 rx2 wy1 c1 ry2 wx2 c2
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step wy1: INSERT INTO b VALUES (1);
+step c1: COMMIT;
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step wx2: DELETE FROM a WHERE i = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx1 rx2 wy1 ry2 c1 wx2 c2
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step wy1: INSERT INTO b VALUES (1);
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step c1: COMMIT;
+step wx2: DELETE FROM a WHERE i = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx1 rx2 wy1 ry2 wx2 c1 c2
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step wy1: INSERT INTO b VALUES (1);
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step wx2: DELETE FROM a WHERE i = 1;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 rx2 wy1 ry2 wx2 c2 c1
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step wy1: INSERT INTO b VALUES (1);
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step wx2: DELETE FROM a WHERE i = 1;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 rx2 ry2 wy1 c1 wx2 c2
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step wy1: INSERT INTO b VALUES (1);
+step c1: COMMIT;
+step wx2: DELETE FROM a WHERE i = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx1 rx2 ry2 wy1 wx2 c1 c2
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step wy1: INSERT INTO b VALUES (1);
+step wx2: DELETE FROM a WHERE i = 1;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 rx2 ry2 wy1 wx2 c2 c1
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step wy1: INSERT INTO b VALUES (1);
+step wx2: DELETE FROM a WHERE i = 1;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 rx2 ry2 wx2 wy1 c1 c2
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step wx2: DELETE FROM a WHERE i = 1;
+step wy1: INSERT INTO b VALUES (1);
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 rx2 ry2 wx2 wy1 c2 c1
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step wx2: DELETE FROM a WHERE i = 1;
+step wy1: INSERT INTO b VALUES (1);
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 rx2 ry2 wx2 c2 wy1 c1
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step wx2: DELETE FROM a WHERE i = 1;
+step c2: COMMIT;
+step wy1: INSERT INTO b VALUES (1);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: rx2 rx1 wy1 c1 ry2 wx2 c2
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step wy1: INSERT INTO b VALUES (1);
+step c1: COMMIT;
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step wx2: DELETE FROM a WHERE i = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx2 rx1 wy1 ry2 c1 wx2 c2
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step wy1: INSERT INTO b VALUES (1);
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step c1: COMMIT;
+step wx2: DELETE FROM a WHERE i = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx2 rx1 wy1 ry2 wx2 c1 c2
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step wy1: INSERT INTO b VALUES (1);
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step wx2: DELETE FROM a WHERE i = 1;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx2 rx1 wy1 ry2 wx2 c2 c1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step wy1: INSERT INTO b VALUES (1);
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step wx2: DELETE FROM a WHERE i = 1;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx2 rx1 ry2 wy1 c1 wx2 c2
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step wy1: INSERT INTO b VALUES (1);
+step c1: COMMIT;
+step wx2: DELETE FROM a WHERE i = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx2 rx1 ry2 wy1 wx2 c1 c2
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step wy1: INSERT INTO b VALUES (1);
+step wx2: DELETE FROM a WHERE i = 1;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx2 rx1 ry2 wy1 wx2 c2 c1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step wy1: INSERT INTO b VALUES (1);
+step wx2: DELETE FROM a WHERE i = 1;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx2 rx1 ry2 wx2 wy1 c1 c2
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step wx2: DELETE FROM a WHERE i = 1;
+step wy1: INSERT INTO b VALUES (1);
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx2 rx1 ry2 wx2 wy1 c2 c1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step wx2: DELETE FROM a WHERE i = 1;
+step wy1: INSERT INTO b VALUES (1);
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx2 rx1 ry2 wx2 c2 wy1 c1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step wx2: DELETE FROM a WHERE i = 1;
+step c2: COMMIT;
+step wy1: INSERT INTO b VALUES (1);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: rx2 ry2 rx1 wy1 c1 wx2 c2
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step wy1: INSERT INTO b VALUES (1);
+step c1: COMMIT;
+step wx2: DELETE FROM a WHERE i = 1;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx2 ry2 rx1 wy1 wx2 c1 c2
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step wy1: INSERT INTO b VALUES (1);
+step wx2: DELETE FROM a WHERE i = 1;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx2 ry2 rx1 wy1 wx2 c2 c1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step wy1: INSERT INTO b VALUES (1);
+step wx2: DELETE FROM a WHERE i = 1;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx2 ry2 rx1 wx2 wy1 c1 c2
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step wx2: DELETE FROM a WHERE i = 1;
+step wy1: INSERT INTO b VALUES (1);
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx2 ry2 rx1 wx2 wy1 c2 c1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step wx2: DELETE FROM a WHERE i = 1;
+step wy1: INSERT INTO b VALUES (1);
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx2 ry2 rx1 wx2 c2 wy1 c1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step wx2: DELETE FROM a WHERE i = 1;
+step c2: COMMIT;
+step wy1: INSERT INTO b VALUES (1);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: rx2 ry2 wx2 rx1 wy1 c1 c2
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step wx2: DELETE FROM a WHERE i = 1;
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step wy1: INSERT INTO b VALUES (1);
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx2 ry2 wx2 rx1 wy1 c2 c1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step wx2: DELETE FROM a WHERE i = 1;
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step wy1: INSERT INTO b VALUES (1);
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx2 ry2 wx2 rx1 c2 wy1 c1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step wx2: DELETE FROM a WHERE i = 1;
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step c2: COMMIT;
+step wy1: INSERT INTO b VALUES (1);
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: rx2 ry2 wx2 c2 rx1 wy1 c1
+step rx2: SELECT i FROM a WHERE i = 1;
+i
+
+1
+step ry2: SELECT a_id FROM b WHERE a_id = 1;
+a_id
+
+step wx2: DELETE FROM a WHERE i = 1;
+step c2: COMMIT;
+step rx1: SELECT i FROM a WHERE i = 1;
+i
+
+step wy1: INSERT INTO b VALUES (1);
+step c1: COMMIT;
diff --git a/src/test/isolation/expected/ri-trigger.out b/src/test/isolation/expected/ri-trigger.out
new file mode 100644
index 00000000000..9709e771953
--- /dev/null
+++ b/src/test/isolation/expected/ri-trigger.out
@@ -0,0 +1,111 @@
+Parsed test spec with 2 sessions
+
+starting permutation: wxry1 c1 r2 wyrx2 c2
+step wxry1: INSERT INTO child (parent_id) VALUES (0);
+step c1: COMMIT;
+step r2: SELECT TRUE;
+bool
+
+t
+step wyrx2: DELETE FROM parent WHERE parent_id = 0;
+ERROR: child row exists
+step c2: COMMIT;
+
+starting permutation: wxry1 r2 c1 wyrx2 c2
+step wxry1: INSERT INTO child (parent_id) VALUES (0);
+step r2: SELECT TRUE;
+bool
+
+t
+step c1: COMMIT;
+step wyrx2: DELETE FROM parent WHERE parent_id = 0;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: wxry1 r2 wyrx2 c1 c2
+step wxry1: INSERT INTO child (parent_id) VALUES (0);
+step r2: SELECT TRUE;
+bool
+
+t
+step wyrx2: DELETE FROM parent WHERE parent_id = 0;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wxry1 r2 wyrx2 c2 c1
+step wxry1: INSERT INTO child (parent_id) VALUES (0);
+step r2: SELECT TRUE;
+bool
+
+t
+step wyrx2: DELETE FROM parent WHERE parent_id = 0;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: r2 wxry1 c1 wyrx2 c2
+step r2: SELECT TRUE;
+bool
+
+t
+step wxry1: INSERT INTO child (parent_id) VALUES (0);
+step c1: COMMIT;
+step wyrx2: DELETE FROM parent WHERE parent_id = 0;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: r2 wxry1 wyrx2 c1 c2
+step r2: SELECT TRUE;
+bool
+
+t
+step wxry1: INSERT INTO child (parent_id) VALUES (0);
+step wyrx2: DELETE FROM parent WHERE parent_id = 0;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: r2 wxry1 wyrx2 c2 c1
+step r2: SELECT TRUE;
+bool
+
+t
+step wxry1: INSERT INTO child (parent_id) VALUES (0);
+step wyrx2: DELETE FROM parent WHERE parent_id = 0;
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: r2 wyrx2 wxry1 c1 c2
+step r2: SELECT TRUE;
+bool
+
+t
+step wyrx2: DELETE FROM parent WHERE parent_id = 0;
+step wxry1: INSERT INTO child (parent_id) VALUES (0);
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: r2 wyrx2 wxry1 c2 c1
+step r2: SELECT TRUE;
+bool
+
+t
+step wyrx2: DELETE FROM parent WHERE parent_id = 0;
+step wxry1: INSERT INTO child (parent_id) VALUES (0);
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: r2 wyrx2 c2 wxry1 c1
+step r2: SELECT TRUE;
+bool
+
+t
+step wyrx2: DELETE FROM parent WHERE parent_id = 0;
+step c2: COMMIT;
+step wxry1: INSERT INTO child (parent_id) VALUES (0);
+ERROR: parent row missing
+step c1: COMMIT;
diff --git a/src/test/isolation/expected/simple-write-skew.out b/src/test/isolation/expected/simple-write-skew.out
new file mode 100644
index 00000000000..5896beec331
--- /dev/null
+++ b/src/test/isolation/expected/simple-write-skew.out
@@ -0,0 +1,41 @@
+Parsed test spec with 2 sessions
+
+starting permutation: rwx1 c1 rwx2 c2
+step rwx1: UPDATE test SET t = 'apple' WHERE t = 'pear';
+step c1: COMMIT;
+step rwx2: UPDATE test SET t = 'pear' WHERE t = 'apple'
+step c2: COMMIT;
+
+starting permutation: rwx1 rwx2 c1 c2
+step rwx1: UPDATE test SET t = 'apple' WHERE t = 'pear';
+step rwx2: UPDATE test SET t = 'pear' WHERE t = 'apple'
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rwx1 rwx2 c2 c1
+step rwx1: UPDATE test SET t = 'apple' WHERE t = 'pear';
+step rwx2: UPDATE test SET t = 'pear' WHERE t = 'apple'
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rwx2 rwx1 c1 c2
+step rwx2: UPDATE test SET t = 'pear' WHERE t = 'apple'
+step rwx1: UPDATE test SET t = 'apple' WHERE t = 'pear';
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rwx2 rwx1 c2 c1
+step rwx2: UPDATE test SET t = 'pear' WHERE t = 'apple'
+step rwx1: UPDATE test SET t = 'apple' WHERE t = 'pear';
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rwx2 c2 rwx1 c1
+step rwx2: UPDATE test SET t = 'pear' WHERE t = 'apple'
+step c2: COMMIT;
+step rwx1: UPDATE test SET t = 'apple' WHERE t = 'pear';
+step c1: COMMIT;
diff --git a/src/test/isolation/expected/temporal-range-integrity.out b/src/test/isolation/expected/temporal-range-integrity.out
new file mode 100644
index 00000000000..3e7fb986905
--- /dev/null
+++ b/src/test/isolation/expected/temporal-range-integrity.out
@@ -0,0 +1,299 @@
+Parsed test spec with 2 sessions
+
+starting permutation: rx1 wy1 c1 ry2 wx2 c2
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+
+1
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step c1: COMMIT;
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+
+1
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step c2: COMMIT;
+
+starting permutation: rx1 wy1 ry2 c1 wx2 c2
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+
+1
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+
+0
+step c1: COMMIT;
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx1 wy1 ry2 wx2 c1 c2
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+
+1
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+
+0
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 wy1 ry2 wx2 c2 c1
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+
+1
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+
+0
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wy1 c1 wx2 c2
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+
+1
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+
+0
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step c1: COMMIT;
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: rx1 ry2 wy1 wx2 c1 c2
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+
+1
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+
+0
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wy1 wx2 c2 c1
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+
+1
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+
+0
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wx2 wy1 c1 c2
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+
+1
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+
+0
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wx2 wy1 c2 c1
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+
+1
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+
+0
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rx1 ry2 wx2 c2 wy1 c1
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+
+1
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+
+0
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step c2: COMMIT;
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: ry2 rx1 wy1 c1 wx2 c2
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+
+0
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+
+1
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step c1: COMMIT;
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: ry2 rx1 wy1 wx2 c1 c2
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+
+0
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+
+1
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 rx1 wy1 wx2 c2 c1
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+
+0
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+
+1
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 rx1 wx2 wy1 c1 c2
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+
+0
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+
+1
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 rx1 wx2 wy1 c2 c1
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+
+0
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+
+1
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 rx1 wx2 c2 wy1 c1
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+
+0
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+
+1
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step c2: COMMIT;
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: ry2 wx2 rx1 wy1 c1 c2
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+
+0
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+
+1
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 wx2 rx1 wy1 c2 c1
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+
+0
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+
+1
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry2 wx2 rx1 c2 wy1 c1
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+
+0
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+
+1
+step c2: COMMIT;
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: ry2 wx2 c2 rx1 wy1 c1
+step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01';
+count
+
+0
+step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01';
+step c2: COMMIT;
+step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15');
+count
+
+0
+step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15');
+step c1: COMMIT;
diff --git a/src/test/isolation/expected/total-cash.out b/src/test/isolation/expected/total-cash.out
new file mode 100644
index 00000000000..df1950843b2
--- /dev/null
+++ b/src/test/isolation/expected/total-cash.out
@@ -0,0 +1,281 @@
+Parsed test spec with 2 sessions
+
+starting permutation: wx1 rxy1 c1 wy2 rxy2 c2
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step rxy1: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step c1: COMMIT;
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step rxy2: SELECT SUM(balance) FROM accounts;
+sum
+
+800
+step c2: COMMIT;
+
+starting permutation: wx1 rxy1 wy2 c1 rxy2 c2
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step rxy1: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step c1: COMMIT;
+step rxy2: SELECT SUM(balance) FROM accounts;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: wx1 rxy1 wy2 rxy2 c1 c2
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step rxy1: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step rxy2: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wx1 rxy1 wy2 rxy2 c2 c1
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step rxy1: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step rxy2: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wx1 wy2 rxy1 c1 rxy2 c2
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step rxy1: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step c1: COMMIT;
+step rxy2: SELECT SUM(balance) FROM accounts;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: wx1 wy2 rxy1 rxy2 c1 c2
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step rxy1: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step rxy2: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wx1 wy2 rxy1 rxy2 c2 c1
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step rxy1: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step rxy2: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wx1 wy2 rxy2 rxy1 c1 c2
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step rxy2: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step rxy1: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wx1 wy2 rxy2 rxy1 c2 c1
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step rxy2: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step rxy1: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wx1 wy2 rxy2 c2 rxy1 c1
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step rxy2: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step c2: COMMIT;
+step rxy1: SELECT SUM(balance) FROM accounts;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: wy2 wx1 rxy1 c1 rxy2 c2
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step rxy1: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step c1: COMMIT;
+step rxy2: SELECT SUM(balance) FROM accounts;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: wy2 wx1 rxy1 rxy2 c1 c2
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step rxy1: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step rxy2: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wy2 wx1 rxy1 rxy2 c2 c1
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step rxy1: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step rxy2: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wy2 wx1 rxy2 rxy1 c1 c2
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step rxy2: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step rxy1: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wy2 wx1 rxy2 rxy1 c2 c1
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step rxy2: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step rxy1: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wy2 wx1 rxy2 c2 rxy1 c1
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step rxy2: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step c2: COMMIT;
+step rxy1: SELECT SUM(balance) FROM accounts;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: wy2 rxy2 wx1 rxy1 c1 c2
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step rxy2: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step rxy1: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wy2 rxy2 wx1 rxy1 c2 c1
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step rxy2: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step rxy1: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step c2: COMMIT;
+step c1: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wy2 rxy2 wx1 c2 rxy1 c1
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step rxy2: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step c2: COMMIT;
+step rxy1: SELECT SUM(balance) FROM accounts;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c1: COMMIT;
+
+starting permutation: wy2 rxy2 c2 wx1 rxy1 c1
+step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings';
+step rxy2: SELECT SUM(balance) FROM accounts;
+sum
+
+1000
+step c2: COMMIT;
+step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
+step rxy1: SELECT SUM(balance) FROM accounts;
+sum
+
+800
+step c1: COMMIT;
diff --git a/src/test/isolation/expected/two-ids.out b/src/test/isolation/expected/two-ids.out
new file mode 100644
index 00000000000..81e61396805
--- /dev/null
+++ b/src/test/isolation/expected/two-ids.out
@@ -0,0 +1,1007 @@
+Parsed test spec with 3 sessions
+
+starting permutation: wx1 c1 rxwy2 c2 ry3 c3
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step ry3: select id from D2;
+id
+
+3
+step c3: COMMIT;
+
+starting permutation: wx1 c1 rxwy2 ry3 c2 c3
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+
+1
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx1 c1 rxwy2 ry3 c3 c2
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+
+1
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx1 c1 ry3 rxwy2 c2 c3
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step ry3: select id from D2;
+id
+
+1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx1 c1 ry3 rxwy2 c3 c2
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step ry3: select id from D2;
+id
+
+1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx1 c1 ry3 c3 rxwy2 c2
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step ry3: select id from D2;
+id
+
+1
+step c3: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+
+starting permutation: wx1 rxwy2 c1 c2 ry3 c3
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c1: COMMIT;
+step c2: COMMIT;
+step ry3: select id from D2;
+id
+
+2
+step c3: COMMIT;
+
+starting permutation: wx1 rxwy2 c1 ry3 c2 c3
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c1: COMMIT;
+step ry3: select id from D2;
+id
+
+1
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT;
+
+starting permutation: wx1 rxwy2 c1 ry3 c3 c2
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c1: COMMIT;
+step ry3: select id from D2;
+id
+
+1
+step c3: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wx1 rxwy2 c2 c1 ry3 c3
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step c1: COMMIT;
+step ry3: select id from D2;
+id
+
+2
+step c3: COMMIT;
+
+starting permutation: wx1 rxwy2 c2 ry3 c1 c3
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step ry3: select id from D2;
+id
+
+2
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx1 rxwy2 c2 ry3 c3 c1
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step ry3: select id from D2;
+id
+
+2
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx1 rxwy2 ry3 c1 c2 c3
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+
+1
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT;
+
+starting permutation: wx1 rxwy2 ry3 c1 c3 c2
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+
+1
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wx1 rxwy2 ry3 c2 c1 c3
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+
+1
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx1 rxwy2 ry3 c2 c3 c1
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+
+1
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx1 rxwy2 ry3 c3 c1 c2
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+
+1
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx1 rxwy2 ry3 c3 c2 c1
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+
+1
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx1 ry3 c1 rxwy2 c2 c3
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+
+1
+step c1: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx1 ry3 c1 rxwy2 c3 c2
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+
+1
+step c1: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx1 ry3 c1 c3 rxwy2 c2
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+
+1
+step c1: COMMIT;
+step c3: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+
+starting permutation: wx1 ry3 rxwy2 c1 c2 c3
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+
+1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT;
+
+starting permutation: wx1 ry3 rxwy2 c1 c3 c2
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+
+1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: wx1 ry3 rxwy2 c2 c1 c3
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+
+1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: wx1 ry3 rxwy2 c2 c3 c1
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+
+1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx1 ry3 rxwy2 c3 c1 c2
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+
+1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx1 ry3 rxwy2 c3 c2 c1
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+
+1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: wx1 ry3 c3 c1 rxwy2 c2
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+
+1
+step c3: COMMIT;
+step c1: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+
+starting permutation: wx1 ry3 c3 rxwy2 c1 c2
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+
+1
+step c3: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: wx1 ry3 c3 rxwy2 c2 c1
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+
+1
+step c3: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy2 wx1 c1 c2 ry3 c3
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step c2: COMMIT;
+step ry3: select id from D2;
+id
+
+2
+step c3: COMMIT;
+
+starting permutation: rxwy2 wx1 c1 ry3 c2 c3
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step ry3: select id from D2;
+id
+
+1
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT;
+
+starting permutation: rxwy2 wx1 c1 ry3 c3 c2
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step ry3: select id from D2;
+id
+
+1
+step c3: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxwy2 wx1 c2 c1 ry3 c3
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c2: COMMIT;
+step c1: COMMIT;
+step ry3: select id from D2;
+id
+
+2
+step c3: COMMIT;
+
+starting permutation: rxwy2 wx1 c2 ry3 c1 c3
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c2: COMMIT;
+step ry3: select id from D2;
+id
+
+2
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy2 wx1 c2 ry3 c3 c1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c2: COMMIT;
+step ry3: select id from D2;
+id
+
+2
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy2 wx1 ry3 c1 c2 c3
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+
+1
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT;
+
+starting permutation: rxwy2 wx1 ry3 c1 c3 c2
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+
+1
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxwy2 wx1 ry3 c2 c1 c3
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+
+1
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy2 wx1 ry3 c2 c3 c1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+
+1
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy2 wx1 ry3 c3 c1 c2
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+
+1
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy2 wx1 ry3 c3 c2 c1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+
+1
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy2 c2 wx1 c1 ry3 c3
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step ry3: select id from D2;
+id
+
+2
+step c3: COMMIT;
+
+starting permutation: rxwy2 c2 wx1 ry3 c1 c3
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+
+2
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy2 c2 wx1 ry3 c3 c1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step wx1: update D1 set id = id + 1;
+step ry3: select id from D2;
+id
+
+2
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy2 c2 ry3 wx1 c1 c3
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step ry3: select id from D2;
+id
+
+2
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy2 c2 ry3 wx1 c3 c1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step ry3: select id from D2;
+id
+
+2
+step wx1: update D1 set id = id + 1;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy2 c2 ry3 c3 wx1 c1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step ry3: select id from D2;
+id
+
+2
+step c3: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+
+starting permutation: rxwy2 ry3 wx1 c1 c2 c3
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+
+1
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT;
+
+starting permutation: rxwy2 ry3 wx1 c1 c3 c2
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+
+1
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: rxwy2 ry3 wx1 c2 c1 c3
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+
+1
+step wx1: update D1 set id = id + 1;
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy2 ry3 wx1 c2 c3 c1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+
+1
+step wx1: update D1 set id = id + 1;
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy2 ry3 wx1 c3 c1 c2
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+
+1
+step wx1: update D1 set id = id + 1;
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy2 ry3 wx1 c3 c2 c1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+
+1
+step wx1: update D1 set id = id + 1;
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy2 ry3 c2 wx1 c1 c3
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+
+1
+step c2: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: rxwy2 ry3 c2 wx1 c3 c1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+
+1
+step c2: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy2 ry3 c2 c3 wx1 c1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+
+1
+step c2: COMMIT;
+step c3: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+
+starting permutation: rxwy2 ry3 c3 wx1 c1 c2
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+
+1
+step c3: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rxwy2 ry3 c3 wx1 c2 c1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+
+1
+step c3: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rxwy2 ry3 c3 c2 wx1 c1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step ry3: select id from D2;
+id
+
+1
+step c3: COMMIT;
+step c2: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+
+starting permutation: ry3 wx1 c1 rxwy2 c2 c3
+step ry3: select id from D2;
+id
+
+1
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step c3: COMMIT;
+
+starting permutation: ry3 wx1 c1 rxwy2 c3 c2
+step ry3: select id from D2;
+id
+
+1
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c3: COMMIT;
+step c2: COMMIT;
+
+starting permutation: ry3 wx1 c1 c3 rxwy2 c2
+step ry3: select id from D2;
+id
+
+1
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step c3: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+
+starting permutation: ry3 wx1 rxwy2 c1 c2 c3
+step ry3: select id from D2;
+id
+
+1
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT;
+
+starting permutation: ry3 wx1 rxwy2 c1 c3 c2
+step ry3: select id from D2;
+id
+
+1
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry3 wx1 rxwy2 c2 c1 c3
+step ry3: select id from D2;
+id
+
+1
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: ry3 wx1 rxwy2 c2 c3 c1
+step ry3: select id from D2;
+id
+
+1
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: ry3 wx1 rxwy2 c3 c1 c2
+step ry3: select id from D2;
+id
+
+1
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: ry3 wx1 rxwy2 c3 c2 c1
+step ry3: select id from D2;
+id
+
+1
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: ry3 wx1 c3 c1 rxwy2 c2
+step ry3: select id from D2;
+id
+
+1
+step wx1: update D1 set id = id + 1;
+step c3: COMMIT;
+step c1: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+
+starting permutation: ry3 wx1 c3 rxwy2 c1 c2
+step ry3: select id from D2;
+id
+
+1
+step wx1: update D1 set id = id + 1;
+step c3: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: ry3 wx1 c3 rxwy2 c2 c1
+step ry3: select id from D2;
+id
+
+1
+step wx1: update D1 set id = id + 1;
+step c3: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: ry3 rxwy2 wx1 c1 c2 c3
+step ry3: select id from D2;
+id
+
+1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+step c3: COMMIT;
+
+starting permutation: ry3 rxwy2 wx1 c1 c3 c2
+step ry3: select id from D2;
+id
+
+1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step c3: COMMIT;
+step c2: COMMIT;
+ERROR: could not serialize access due to read/write dependencies among transactions
+
+starting permutation: ry3 rxwy2 wx1 c2 c1 c3
+step ry3: select id from D2;
+id
+
+1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c2: COMMIT;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: ry3 rxwy2 wx1 c2 c3 c1
+step ry3: select id from D2;
+id
+
+1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c2: COMMIT;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: ry3 rxwy2 wx1 c3 c1 c2
+step ry3: select id from D2;
+id
+
+1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c3: COMMIT;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: ry3 rxwy2 wx1 c3 c2 c1
+step ry3: select id from D2;
+id
+
+1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c3: COMMIT;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: ry3 rxwy2 c2 wx1 c1 c3
+step ry3: select id from D2;
+id
+
+1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step c3: COMMIT;
+
+starting permutation: ry3 rxwy2 c2 wx1 c3 c1
+step ry3: select id from D2;
+id
+
+1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c3: COMMIT;
+step c1: COMMIT;
+
+starting permutation: ry3 rxwy2 c2 c3 wx1 c1
+step ry3: select id from D2;
+id
+
+1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step c3: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+
+starting permutation: ry3 rxwy2 c3 wx1 c1 c2
+step ry3: select id from D2;
+id
+
+1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c3: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: ry3 rxwy2 c3 wx1 c2 c1
+step ry3: select id from D2;
+id
+
+1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c3: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: ry3 rxwy2 c3 c2 wx1 c1
+step ry3: select id from D2;
+id
+
+1
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c3: COMMIT;
+step c2: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+
+starting permutation: ry3 c3 wx1 c1 rxwy2 c2
+step ry3: select id from D2;
+id
+
+1
+step c3: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+
+starting permutation: ry3 c3 wx1 rxwy2 c1 c2
+step ry3: select id from D2;
+id
+
+1
+step c3: COMMIT;
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: ry3 c3 wx1 rxwy2 c2 c1
+step ry3: select id from D2;
+id
+
+1
+step c3: COMMIT;
+step wx1: update D1 set id = id + 1;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: ry3 c3 rxwy2 wx1 c1 c2
+step ry3: select id from D2;
+id
+
+1
+step c3: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: ry3 c3 rxwy2 wx1 c2 c1
+step ry3: select id from D2;
+id
+
+1
+step c3: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step wx1: update D1 set id = id + 1;
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: ry3 c3 rxwy2 c2 wx1 c1
+step ry3: select id from D2;
+id
+
+1
+step c3: COMMIT;
+step rxwy2: update D2 set id = (select id+1 from D1);
+step c2: COMMIT;
+step wx1: update D1 set id = id + 1;
+step c1: COMMIT;
diff --git a/src/test/isolation/isolation_main.c b/src/test/isolation/isolation_main.c
new file mode 100644
index 00000000000..7d2cfa2ea82
--- /dev/null
+++ b/src/test/isolation/isolation_main.c
@@ -0,0 +1,89 @@
+/*-------------------------------------------------------------------------
+ *
+ * isolation_main --- pg_regress test launcher for isolation tests
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/test/isolation/isolation_main.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "../regress/pg_regress.h"
+
+/*
+ * start an isolation tester process for specified file (including
+ * redirection), and return process ID
+ */
+static PID_TYPE
+isolation_start_test(const char *testname,
+ _stringlist ** resultfiles,
+ _stringlist ** expectfiles,
+ _stringlist ** tags)
+{
+ PID_TYPE pid;
+ char infile[MAXPGPATH];
+ char outfile[MAXPGPATH];
+ char expectfile[MAXPGPATH];
+ char psql_cmd[MAXPGPATH * 3];
+ size_t offset = 0;
+
+ /*
+ * Look for files in the output dir first, consistent with a vpath search.
+ * This is mainly to create more reasonable error messages if the file is
+ * not found. It also allows local test overrides when running pg_regress
+ * outside of the source tree.
+ */
+ snprintf(infile, sizeof(infile), "%s/specs/%s.spec",
+ outputdir, testname);
+ if (!file_exists(infile))
+ snprintf(infile, sizeof(infile), "%s/specs/%s.spec",
+ inputdir, testname);
+
+ snprintf(outfile, sizeof(outfile), "%s/results/%s.out",
+ outputdir, testname);
+
+ snprintf(expectfile, sizeof(expectfile), "%s/expected/%s.out",
+ outputdir, testname);
+ if (!file_exists(expectfile))
+ snprintf(expectfile, sizeof(expectfile), "%s/expected/%s.out",
+ inputdir, testname);
+
+ add_stringlist_item(resultfiles, outfile);
+ add_stringlist_item(expectfiles, expectfile);
+
+ if (launcher)
+ offset += snprintf(psql_cmd + offset, sizeof(psql_cmd) - offset,
+ "%s ", launcher);
+
+ snprintf(psql_cmd + offset, sizeof(psql_cmd) - offset,
+ SYSTEMQUOTE "./isolationtester \"dbname=%s\" < \"%s\" > \"%s\" 2>&1" SYSTEMQUOTE,
+ dblist->str,
+ infile,
+ outfile);
+
+ pid = spawn_process(psql_cmd);
+
+ if (pid == INVALID_PID)
+ {
+ fprintf(stderr, _("could not start process for test %s\n"),
+ testname);
+ exit_nicely(2);
+ }
+
+ return pid;
+}
+
+static void
+isolation_init(void)
+{
+ /* set default regression database name */
+ add_stringlist_item(&dblist, "isolationtest");
+}
+
+int
+main(int argc, char *argv[])
+{
+ return regression_main(argc, argv, isolation_init, isolation_start_test);
+}
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
new file mode 100644
index 00000000000..6ea8a29f490
--- /dev/null
+++ b/src/test/isolation/isolation_schedule
@@ -0,0 +1,11 @@
+test: simple-write-skew
+test: receipt-report
+test: temporal-range-integrity
+test: project-manager
+test: classroom-scheduling
+test: total-cash
+test: referential-integrity
+test: ri-trigger
+test: partial-index
+test: two-ids
+test: multiple-row-versions
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
new file mode 100644
index 00000000000..99460519456
--- /dev/null
+++ b/src/test/isolation/isolationtester.c
@@ -0,0 +1,372 @@
+/*
+ * src/test/isolation/isolationtester.c
+ *
+ * isolationtester.c
+ * Runs an isolation test specified by a spec file.
+ */
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "libpq-fe.h"
+
+#include "isolationtester.h"
+
+static PGconn **conns = NULL;
+static int nconns = 0;
+
+static void run_all_permutations(TestSpec *testspec);
+static void run_all_permutations_recurse(TestSpec *testspec, int nsteps, Step **steps);
+static void run_named_permutations(TestSpec *testspec);
+static void run_permutation(TestSpec *testspec, int nsteps, Step **steps);
+
+static int step_qsort_cmp(const void *a, const void *b);
+static int step_bsearch_cmp(const void *a, const void *b);
+
+static void printResultSet(PGresult *res);
+
+/* close all connections and exit */
+static void
+exit_nicely(void)
+{
+ int i;
+ for (i = 0; i < nconns; i++)
+ PQfinish(conns[i]);
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ const char *conninfo;
+ TestSpec *testspec;
+ int i;
+
+ /*
+ * If the user supplies a parameter on the command line, use it as the
+ * conninfo string; otherwise default to setting dbname=postgres and
+ * using environment variables or defaults for all other connection
+ * parameters.
+ */
+ if (argc > 1)
+ conninfo = argv[1];
+ else
+ conninfo = "dbname = postgres";
+
+ /* Read the test spec from stdin */
+ spec_yyparse();
+ testspec = &parseresult;
+ printf("Parsed test spec with %d sessions\n", testspec->nsessions);
+
+ /* Establish connections to the database, one for each session */
+ nconns = testspec->nsessions;
+ conns = calloc(nconns, sizeof(PGconn *));
+ for (i = 0; i < testspec->nsessions; i++)
+ {
+ PGresult *res;
+
+ conns[i] = PQconnectdb(conninfo);
+ if (PQstatus(conns[i]) != CONNECTION_OK)
+ {
+ fprintf(stderr, "Connection %d to database failed: %s",
+ i, PQerrorMessage(conns[i]));
+ exit_nicely();
+ }
+
+ /*
+ * Suppress NOTIFY messages, which otherwise pop into results at odd
+ * places.
+ */
+ res = PQexec(conns[i], "SET client_min_messages = warning;");
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "message level setup failed: %s", PQerrorMessage(conns[i]));
+ exit_nicely();
+ }
+ PQclear(res);
+ }
+
+ /* Set the session index fields in steps. */
+ for (i = 0; i < testspec->nsessions; i++)
+ {
+ Session *session = testspec->sessions[i];
+ int stepindex;
+ for (stepindex = 0; stepindex < session->nsteps; stepindex++)
+ session->steps[stepindex]->session = i;
+ }
+
+ /*
+ * Run the permutations specified in the spec, or all if none were
+ * explicitly specified.
+ */
+ if (testspec->permutations)
+ run_named_permutations(testspec);
+ else
+ run_all_permutations(testspec);
+
+ /* Clean up and exit */
+ for (i = 0; i < nconns; i++)
+ PQfinish(conns[i]);
+ return 0;
+}
+
+static int *piles;
+
+/*
+ * Run all permutations of the steps and sessions.
+ */
+static void
+run_all_permutations(TestSpec *testspec)
+{
+ int nsteps;
+ int i;
+ Step **steps;
+
+ /* Count the total number of steps in all sessions */
+ nsteps = 0;
+ for (i = 0; i < testspec->nsessions; i++)
+ nsteps += testspec->sessions[i]->nsteps;
+
+ steps = malloc(sizeof(Step *) * nsteps);
+
+ /*
+ * To generate the permutations, we conceptually put the steps of
+ * each session on a pile. To generate a permuation, we pick steps
+ * from the piles until all piles are empty. By picking steps from
+ * piles in different order, we get different permutations.
+ *
+ * A pile is actually just an integer which tells how many steps
+ * we've already picked from this pile.
+ */
+ piles = malloc(sizeof(int) * testspec->nsessions);
+ for (i = 0; i < testspec->nsessions; i++)
+ piles[i] = 0;
+
+ run_all_permutations_recurse(testspec, 0, steps);
+}
+
+static void
+run_all_permutations_recurse(TestSpec *testspec, int nsteps, Step **steps)
+{
+ int i;
+ int found = 0;
+
+ for (i = 0; i < testspec->nsessions; i++)
+ {
+ /* If there's any more steps in this pile, pick it and recurse */
+ if (piles[i] < testspec->sessions[i]->nsteps)
+ {
+ steps[nsteps] = testspec->sessions[i]->steps[piles[i]];
+ piles[i]++;
+
+ run_all_permutations_recurse(testspec, nsteps + 1, steps);
+
+ piles[i]--;
+
+ found = 1;
+ }
+ }
+
+ /* If all the piles were empty, this permutation is completed. Run it */
+ if (!found)
+ run_permutation(testspec, nsteps, steps);
+}
+
+/*
+ * Run permutations given in the test spec
+ */
+static void
+run_named_permutations(TestSpec *testspec)
+{
+ int i, j;
+ int n;
+ int nallsteps;
+ Step **allsteps;
+
+ /* First create a lookup table of all steps */
+ nallsteps = 0;
+ for (i = 0; i < testspec->nsessions; i++)
+ nallsteps += testspec->sessions[i]->nsteps;
+
+ allsteps = malloc(nallsteps * sizeof(Step *));
+
+ n = 0;
+ for (i = 0; i < testspec->nsessions; i++)
+ {
+ for (j = 0; j < testspec->sessions[i]->nsteps; j++)
+ allsteps[n++] = testspec->sessions[i]->steps[j];
+ }
+
+ qsort(allsteps, nallsteps, sizeof(Step *), &step_qsort_cmp);
+
+ for (i = 0; i < testspec->npermutations; i++)
+ {
+ Permutation *p = testspec->permutations[i];
+ Step **steps;
+
+ steps = malloc(p->nsteps * sizeof(Step *));
+
+ /* Find all the named steps from the lookup table */
+ for (j = 0; j < p->nsteps; j++)
+ {
+ steps[j] = *((Step **) bsearch(p->stepnames[j], allsteps, nallsteps,
+ sizeof(Step *), &step_bsearch_cmp));
+ if (steps[j] == NULL)
+ {
+ fprintf(stderr, "undefined step \"%s\" specified in permutation\n", p->stepnames[j]);
+ exit_nicely();
+ }
+ }
+
+ run_permutation(testspec, p->nsteps, steps);
+ free(steps);
+ }
+}
+
+static int
+step_qsort_cmp(const void *a, const void *b)
+{
+ Step *stepa = *((Step **) a);
+ Step *stepb = *((Step **) b);
+
+ return strcmp(stepa->name, stepb->name);
+}
+
+static int
+step_bsearch_cmp(const void *a, const void *b)
+{
+ char *stepname = (char *) a;
+ Step *step = *((Step **) b);
+
+ return strcmp(stepname, step->name);
+}
+
+/*
+ * Run one permutation
+ */
+static void
+run_permutation(TestSpec *testspec, int nsteps, Step **steps)
+{
+ PGresult *res;
+ int i;
+
+ printf("\nstarting permutation:");
+ for (i = 0; i < nsteps; i++)
+ printf(" %s", steps[i]->name);
+ printf("\n");
+
+ /* Perform setup */
+ if (testspec->setupsql)
+ {
+ res = PQexec(conns[0], testspec->setupsql);
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "setup failed: %s", PQerrorMessage(conns[0]));
+ exit_nicely();
+ }
+ PQclear(res);
+ }
+
+ /* Perform per-session setup */
+ for (i = 0; i < testspec->nsessions; i++)
+ {
+ if (testspec->sessions[i]->setupsql)
+ {
+ res = PQexec(conns[i], testspec->sessions[i]->setupsql);
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "setup of session %s failed: %s",
+ testspec->sessions[i]->name,
+ PQerrorMessage(conns[0]));
+ exit_nicely();
+ }
+ PQclear(res);
+ }
+ }
+
+ /* Perform steps */
+ for (i = 0; i < nsteps; i++)
+ {
+ Step *step = steps[i];
+ printf("step %s: %s\n", step->name, step->sql);
+ res = PQexec(conns[step->session], step->sql);
+
+ switch(PQresultStatus(res))
+ {
+ case PGRES_COMMAND_OK:
+ break;
+
+ case PGRES_TUPLES_OK:
+ printResultSet(res);
+ break;
+
+ case PGRES_FATAL_ERROR:
+ /* Detail may contain xid values, so just show primary. */
+ printf("%s: %s\n", PQresultErrorField(res, PG_DIAG_SEVERITY),
+ PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY));
+ break;
+
+ default:
+ printf("unexpected result status: %s\n",
+ PQresStatus(PQresultStatus(res)));
+ }
+ PQclear(res);
+ }
+
+ /* Perform per-session teardown */
+ for (i = 0; i < testspec->nsessions; i++)
+ {
+ if (testspec->sessions[i]->teardownsql)
+ {
+ res = PQexec(conns[i], testspec->sessions[i]->teardownsql);
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "teardown of session %s failed: %s",
+ testspec->sessions[i]->name,
+ PQerrorMessage(conns[0]));
+ /* don't exit on teardown failure */
+ }
+ PQclear(res);
+ }
+ }
+
+ /* Perform teardown */
+ if (testspec->teardownsql)
+ {
+ res = PQexec(conns[0], testspec->teardownsql);
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "teardown failed: %s",
+ PQerrorMessage(conns[0]));
+ /* don't exit on teardown failure */
+
+ }
+ PQclear(res);
+ }
+}
+
+static void
+printResultSet(PGresult *res)
+{
+ int nFields;
+ int i, j;
+
+ /* first, print out the attribute names */
+ nFields = PQnfields(res);
+ for (i = 0; i < nFields; i++)
+ printf("%-15s", PQfname(res, i));
+ printf("\n\n");
+
+ /* next, print out the rows */
+ for (i = 0; i < PQntuples(res); i++)
+ {
+ for (j = 0; j < nFields; j++)
+ printf("%-15s", PQgetvalue(res, i, j));
+ printf("\n");
+ }
+}
diff --git a/src/test/isolation/isolationtester.h b/src/test/isolation/isolationtester.h
new file mode 100644
index 00000000000..b092ed16a0f
--- /dev/null
+++ b/src/test/isolation/isolationtester.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * bootstrap.h
+ * include file for the bootstrapping code
+ *
+ *
+ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL: pgsql/src/include/bootstrap/bootstrap.h,v 1.44 2006/10/04 00:30:07 momjian Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef ISOLATIONTESTER_H
+#define ISOLATIONTESTER_H
+
+typedef struct Session Session;
+typedef struct Step Step;
+
+struct Session
+{
+ char *name;
+ char *setupsql;
+ char *teardownsql;
+ Step **steps;
+ int nsteps;
+};
+
+struct Step
+{
+ int session;
+ char *name;
+ char *sql;
+};
+
+typedef struct
+{
+ int nsteps;
+ char **stepnames;
+} Permutation;
+
+typedef struct
+{
+ char *setupsql;
+ char *teardownsql;
+ Session **sessions;
+ int nsessions;
+ Permutation **permutations;
+ int npermutations;
+} TestSpec;
+
+extern TestSpec parseresult;
+
+extern int spec_yyparse(void);
+
+extern int spec_yylex(void);
+extern void spec_yyerror(const char *str);
+
+#endif /* ISOLATIONTESTER_H */
diff --git a/src/test/isolation/specparse.y b/src/test/isolation/specparse.y
new file mode 100644
index 00000000000..c6847802163
--- /dev/null
+++ b/src/test/isolation/specparse.y
@@ -0,0 +1,188 @@
+%{
+/*-------------------------------------------------------------------------
+ *
+ * specparse.y
+ * bison grammar for the isolation test file format
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "isolationtester.h"
+
+
+TestSpec parseresult; /* result of parsing is left here */
+
+%}
+
+%expect 0
+%name-prefix="spec_yy"
+
+%union
+{
+ char *str;
+ Session *session;
+ Step *step;
+ Permutation *permutation;
+ struct
+ {
+ void **elements;
+ int nelements;
+ } ptr_list;
+}
+
+%type <str> opt_setup opt_teardown
+%type <ptr_list> step_list session_list permutation_list opt_permutation_list
+%type <ptr_list> string_list
+%type <session> session
+%type <step> step
+%type <permutation> permutation
+
+%token <str> sqlblock string
+%token PERMUTATION SESSION SETUP STEP TEARDOWN TEST
+
+%%
+
+TestSpec:
+ opt_setup
+ opt_teardown
+ session_list
+ opt_permutation_list
+ {
+ parseresult.setupsql = $1;
+ parseresult.teardownsql = $2;
+ parseresult.sessions = (Session **) $3.elements;
+ parseresult.nsessions = $3.nelements;
+ parseresult.permutations = (Permutation **) $4.elements;
+ parseresult.npermutations = $4.nelements;
+ }
+ ;
+
+opt_setup:
+ /* EMPTY */ { $$ = NULL; }
+ | SETUP sqlblock { $$ = $2; }
+ ;
+
+opt_teardown:
+ /* EMPTY */ { $$ = NULL; }
+ | TEARDOWN sqlblock { $$ = $2; }
+ ;
+
+session_list:
+ session_list session
+ {
+ $$.elements = realloc($1.elements,
+ ($1.nelements + 1) * sizeof(void *));
+ $$.elements[$1.nelements] = $2;
+ $$.nelements = $1.nelements + 1;
+ }
+ | session
+ {
+ $$.nelements = 1;
+ $$.elements = malloc(sizeof(void *));
+ $$.elements[0] = $1;
+ }
+ ;
+
+session:
+ SESSION string opt_setup step_list opt_teardown
+ {
+ $$ = malloc(sizeof(Session));
+ $$->name = $2;
+ $$->setupsql = $3;
+ $$->steps = (Step **) $4.elements;
+ $$->nsteps = $4.nelements;
+ $$->teardownsql = $5;
+ }
+ ;
+
+step_list:
+ step_list step
+ {
+ $$.elements = realloc($1.elements,
+ ($1.nelements + 1) * sizeof(void *));
+ $$.elements[$1.nelements] = $2;
+ $$.nelements = $1.nelements + 1;
+ }
+ | step
+ {
+ $$.nelements = 1;
+ $$.elements = malloc(sizeof(void *));
+ $$.elements[0] = $1;
+ }
+ ;
+
+
+step:
+ STEP string sqlblock
+ {
+ $$ = malloc(sizeof(Step));
+ $$->name = $2;
+ $$->sql = $3;
+ }
+ ;
+
+
+opt_permutation_list:
+ permutation_list
+ {
+ $$ = $1;
+ }
+ | /* EMPTY */
+ {
+ $$.elements = NULL;
+ $$.nelements = 0;
+ }
+
+permutation_list:
+ permutation_list permutation
+ {
+ $$.elements = realloc($1.elements,
+ ($1.nelements + 1) * sizeof(void *));
+ $$.elements[$1.nelements] = $2;
+ $$.nelements = $1.nelements + 1;
+ }
+ | permutation
+ {
+ $$.nelements = 1;
+ $$.elements = malloc(sizeof(void *));
+ $$.elements[0] = $1;
+ }
+ ;
+
+
+permutation:
+ PERMUTATION string_list
+ {
+ $$ = malloc(sizeof(Permutation));
+ $$->stepnames = (char **) $2.elements;
+ $$->nsteps = $2.nelements;
+ }
+ ;
+
+string_list:
+ string_list string
+ {
+ $$.elements = realloc($1.elements,
+ ($1.nelements + 1) * sizeof(void *));
+ $$.elements[$1.nelements] = $2;
+ $$.nelements = $1.nelements + 1;
+ }
+ | string
+ {
+ $$.nelements = 1;
+ $$.elements = malloc(sizeof(void *));
+ $$.elements[0] = $1;
+ }
+ ;
+
+%%
+
+#include "specscanner.c"
diff --git a/src/test/isolation/specs/classroom-scheduling.spec b/src/test/isolation/specs/classroom-scheduling.spec
new file mode 100644
index 00000000000..a31565b9cd7
--- /dev/null
+++ b/src/test/isolation/specs/classroom-scheduling.spec
@@ -0,0 +1,29 @@
+# Classroom Scheduling test
+#
+# Ensure that the classroom is not scheduled more than once
+# for any moment in time.
+#
+# Any overlap between the transactions must cause a serialization failure.
+
+setup
+{
+ CREATE TABLE room_reservation (room_id text NOT NULL, start_time timestamp with time zone NOT NULL, end_time timestamp with time zone NOT NULL, description text NOT NULL, CONSTRAINT room_reservation_pkey PRIMARY KEY (room_id, start_time));
+ INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 10:00', TIMESTAMP WITH TIME ZONE '2010-04-01 11:00', 'Bob');
+}
+
+teardown
+{
+ DROP TABLE room_reservation;
+}
+
+session "s1"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "rx1" { SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00'; }
+step "wy1" { INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol'); }
+step "c1" { COMMIT; }
+
+session "s2"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "ry2" { SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30'; }
+step "wx2" { UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00'; }
+step "c2" { COMMIT; }
diff --git a/src/test/isolation/specs/multiple-row-versions.spec b/src/test/isolation/specs/multiple-row-versions.spec
new file mode 100644
index 00000000000..8cfe3a44dcb
--- /dev/null
+++ b/src/test/isolation/specs/multiple-row-versions.spec
@@ -0,0 +1,48 @@
+# Multiple Row Versions test
+#
+# This test is designed to ensure that predicate locks taken on one version
+# of a row are detected as conflicts when a later version of the row is
+# updated or deleted by a transaction concurrent to the reader.
+#
+# Due to long permutation setup time, we are only testing one specific
+# permutation, which should get a serialization error.
+
+setup
+{
+ CREATE TABLE t (id int NOT NULL, txt text) WITH (fillfactor=50);
+ INSERT INTO t (id)
+ SELECT x FROM (SELECT * FROM generate_series(1, 1000000)) a(x);
+ ALTER TABLE t ADD PRIMARY KEY (id);
+}
+
+teardown
+{
+ DROP TABLE t;
+}
+
+session "s1"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "rx1" { SELECT * FROM t WHERE id = 1000000; }
+# delay until after T3 commits
+step "wz1" { UPDATE t SET txt = 'a' WHERE id = 1; }
+step "c1" { COMMIT; }
+
+session "s2"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "wx2" { UPDATE t SET txt = 'b' WHERE id = 1000000; }
+step "c2" { COMMIT; }
+
+session "s3"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "wx3" { UPDATE t SET txt = 'c' WHERE id = 1000000; }
+step "ry3" { SELECT * FROM t WHERE id = 500000; }
+# delay until after T4 commits
+step "c3" { COMMIT; }
+
+session "s4"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "wy4" { UPDATE t SET txt = 'd' WHERE id = 500000; }
+step "rz4" { SELECT * FROM t WHERE id = 1; }
+step "c4" { COMMIT; }
+
+permutation "rx1" "wx2" "c2" "wx3" "ry3" "wy4" "rz4" "c4" "c3" "wz1" "c1"
diff --git a/src/test/isolation/specs/partial-index.spec b/src/test/isolation/specs/partial-index.spec
new file mode 100644
index 00000000000..74e72645d97
--- /dev/null
+++ b/src/test/isolation/specs/partial-index.spec
@@ -0,0 +1,32 @@
+# Partial Index test
+#
+# Make sure that an update which moves a row out of a partial index
+# is handled correctly. In early versions, an attempt at optimization
+# broke this behavior, allowing anomalies.
+#
+# Any overlap between the transactions must cause a serialization failure.
+
+setup
+{
+ create table test_t (id integer, val1 text, val2 integer);
+ create index test_idx on test_t(id) where val2 = 1;
+ insert into test_t (select generate_series(0, 10000), 'a', 2);
+ insert into test_t (select generate_series(0, 10), 'a', 1);
+}
+
+teardown
+{
+ DROP TABLE test_t;
+}
+
+session "s1"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "rxy1" { select * from test_t where val2 = 1; }
+step "wx1" { update test_t set val2 = 2 where val2 = 1 and id = 10; }
+step "c1" { COMMIT; }
+
+session "s2"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "wy2" { update test_t set val2 = 2 where val2 = 1 and id = 9; }
+step "rxy2" { select * from test_t where val2 = 1; }
+step "c2" { COMMIT; }
diff --git a/src/test/isolation/specs/project-manager.spec b/src/test/isolation/specs/project-manager.spec
new file mode 100644
index 00000000000..884012dd89c
--- /dev/null
+++ b/src/test/isolation/specs/project-manager.spec
@@ -0,0 +1,30 @@
+# Project Manager test
+#
+# Ensure that the person who is on the project as a manager
+# is flagged as a project manager in the person table.
+#
+# Any overlap between the transactions must cause a serialization failure.
+
+setup
+{
+ CREATE TABLE person (person_id int NOT NULL PRIMARY KEY, name text NOT NULL, is_project_manager bool NOT NULL);
+ INSERT INTO person VALUES (1, 'Robert Haas', true);
+ CREATE TABLE project (project_no int NOT NULL PRIMARY KEY, description text NOT NULL, project_manager int NOT NULL);
+}
+
+teardown
+{
+ DROP TABLE person, project;
+}
+
+session "s1"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "rx1" { SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager; }
+step "wy1" { INSERT INTO project VALUES (101, 'Build Great Wall', 1); }
+step "c1" { COMMIT; }
+
+session "s2"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "ry2" { SELECT count(*) FROM project WHERE project_manager = 1; }
+step "wx2" { UPDATE person SET is_project_manager = false WHERE person_id = 1; }
+step "c2" { COMMIT; }
diff --git a/src/test/isolation/specs/receipt-report.spec b/src/test/isolation/specs/receipt-report.spec
new file mode 100644
index 00000000000..1e214960d18
--- /dev/null
+++ b/src/test/isolation/specs/receipt-report.spec
@@ -0,0 +1,47 @@
+# Daily Report of Receipts test.
+#
+# This test doesn't persist a bad state in the database; rather, it
+# provides a view of the data which is not consistent with any
+# order of execution of the serializable transactions. It
+# demonstrates a situation where the deposit date for receipts could
+# be changed and a report of the closed day's receipts subsequently
+# run which will miss a receipt from the date which has been closed.
+#
+# There are only six permuations which must cause a serialization failure.
+# Failure cases are where s1 overlaps both s2 and s3, but s2 commits before
+# s3 executes its first SELECT.
+#
+# As long as s3 is declared READ ONLY there should be no false positives.
+# If s3 were changed to READ WRITE, we would currently expect 42 false
+# positives. Further work dealing with de facto READ ONLY transactions
+# may be able to reduce or eliminate those false positives.
+
+setup
+{
+ CREATE TABLE ctl (k text NOT NULL PRIMARY KEY, deposit_date date NOT NULL);
+ INSERT INTO ctl VALUES ('receipt', DATE '2008-12-22');
+ CREATE TABLE receipt (receipt_no int NOT NULL PRIMARY KEY, deposit_date date NOT NULL, amount numeric(13,2));
+ INSERT INTO receipt VALUES (1, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 1.00);
+ INSERT INTO receipt VALUES (2, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 2.00);
+}
+
+teardown
+{
+ DROP TABLE ctl, receipt;
+}
+
+session "s1"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "rxwy1" { INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); }
+step "c1" { COMMIT; }
+
+session "s2"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "wx2" { UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; }
+step "c2" { COMMIT; }
+
+session "s3"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE, READ ONLY; }
+step "rx3" { SELECT * FROM ctl WHERE k = 'receipt'; }
+step "ry3" { SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; }
+step "c3" { COMMIT; }
diff --git a/src/test/isolation/specs/referential-integrity.spec b/src/test/isolation/specs/referential-integrity.spec
new file mode 100644
index 00000000000..0bea3eab973
--- /dev/null
+++ b/src/test/isolation/specs/referential-integrity.spec
@@ -0,0 +1,32 @@
+# Referential Integrity test
+#
+# The assumption here is that the application code issuing the SELECT
+# to test for the presence or absence of a related record would do the
+# right thing -- this script doesn't include that logic.
+#
+# Any overlap between the transactions must cause a serialization failure.
+
+setup
+{
+ CREATE TABLE a (i int PRIMARY KEY);
+ CREATE TABLE b (a_id int);
+ INSERT INTO a VALUES (1);
+}
+
+teardown
+{
+ DROP TABLE a, b;
+}
+
+session "s1"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "rx1" { SELECT i FROM a WHERE i = 1; }
+step "wy1" { INSERT INTO b VALUES (1); }
+step "c1" { COMMIT; }
+
+session "s2"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "rx2" { SELECT i FROM a WHERE i = 1; }
+step "ry2" { SELECT a_id FROM b WHERE a_id = 1; }
+step "wx2" { DELETE FROM a WHERE i = 1; }
+step "c2" { COMMIT; }
diff --git a/src/test/isolation/specs/ri-trigger.spec b/src/test/isolation/specs/ri-trigger.spec
new file mode 100644
index 00000000000..78d1f2f226d
--- /dev/null
+++ b/src/test/isolation/specs/ri-trigger.spec
@@ -0,0 +1,53 @@
+# RI Trigger test
+#
+# Test trigger-based referential integrity enforcement.
+#
+# Any overlap between the transactions must cause a serialization failure.
+
+setup
+{
+ CREATE TABLE parent (parent_id SERIAL NOT NULL PRIMARY KEY);
+ CREATE TABLE child (child_id SERIAL NOT NULL PRIMARY KEY, parent_id INTEGER NOT NULL);
+ CREATE FUNCTION ri_parent() RETURNS TRIGGER LANGUAGE PLPGSQL AS $body$
+ BEGIN
+ PERFORM TRUE FROM child WHERE parent_id = OLD.parent_id;
+ IF FOUND THEN
+ RAISE SQLSTATE '23503' USING MESSAGE = 'child row exists';
+ END IF;
+ IF TG_OP = 'DELETE' THEN
+ RETURN OLD;
+ END IF;
+ RETURN NEW;
+ END;
+ $body$;
+ CREATE TRIGGER ri_parent BEFORE UPDATE OR DELETE ON parent FOR EACH ROW EXECUTE PROCEDURE ri_parent();
+ CREATE FUNCTION ri_child() RETURNS TRIGGER LANGUAGE PLPGSQL AS $body$
+ BEGIN
+ PERFORM TRUE FROM parent WHERE parent_id = NEW.parent_id;
+ IF NOT FOUND THEN
+ RAISE SQLSTATE '23503' USING MESSAGE = 'parent row missing';
+ END IF;
+ RETURN NEW;
+ END;
+ $body$;
+ CREATE TRIGGER ri_child BEFORE INSERT OR UPDATE ON child FOR EACH ROW EXECUTE PROCEDURE ri_child();
+ INSERT INTO parent VALUES(0);
+}
+
+teardown
+{
+ DROP TABLE parent, child;
+ DROP FUNCTION ri_parent();
+ DROP FUNCTION ri_child();
+}
+
+session "s1"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "wxry1" { INSERT INTO child (parent_id) VALUES (0); }
+step "c1" { COMMIT; }
+
+session "s2"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "r2" { SELECT TRUE; }
+step "wyrx2" { DELETE FROM parent WHERE parent_id = 0; }
+step "c2" { COMMIT; }
diff --git a/src/test/isolation/specs/simple-write-skew.spec b/src/test/isolation/specs/simple-write-skew.spec
new file mode 100644
index 00000000000..0aee43f38ed
--- /dev/null
+++ b/src/test/isolation/specs/simple-write-skew.spec
@@ -0,0 +1,30 @@
+# Write skew test.
+#
+# This test has two serializable transactions: one which updates all
+# 'apple' rows to 'pear' and one which updates all 'pear' rows to
+# 'apple'. If these were serialized (run one at a time) either
+# value could be present, but not both. One must be rolled back to
+# prevent the write skew anomaly.
+#
+# Any overlap between the transactions must cause a serialization failure.
+
+setup
+{
+ CREATE TABLE test (i int PRIMARY KEY, t text);
+ INSERT INTO test VALUES (5, 'apple'), (7, 'pear'), (11, 'banana');
+}
+
+teardown
+{
+ DROP TABLE test;
+}
+
+session "s1"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "rwx1" { UPDATE test SET t = 'apple' WHERE t = 'pear'; }
+step "c1" { COMMIT; }
+
+session "s2"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "rwx2" { UPDATE test SET t = 'pear' WHERE t = 'apple'}
+step "c2" { COMMIT; }
diff --git a/src/test/isolation/specs/temporal-range-integrity.spec b/src/test/isolation/specs/temporal-range-integrity.spec
new file mode 100644
index 00000000000..63784ce0a78
--- /dev/null
+++ b/src/test/isolation/specs/temporal-range-integrity.spec
@@ -0,0 +1,38 @@
+# Temporal Range Integrity test
+#
+# Snapshot integrity fails with simple referential integrity tests,
+# but those don't make for good demonstrations because people just
+# say that foreign key definitions should be used instead. There
+# are many integrity tests which are conceptually very similar but
+# don't have built-in support which will fail when used in triggers.
+# This is intended to illustrate such cases. It is obviously very
+# hard to exercise all these permutations when the code is actually
+# in a trigger; this test pulls what would normally be inside of
+# triggers out to the top level to control the permutations.
+#
+# Any overlap between the transactions must cause a serialization failure.
+
+
+setup
+{
+ CREATE TABLE statute (statute_cite text NOT NULL, eff_date date NOT NULL, exp_date date, CONSTRAINT statute_pkey PRIMARY KEY (statute_cite, eff_date));
+ INSERT INTO statute VALUES ('123.45(1)a', DATE '2008-01-01', NULL);
+ CREATE TABLE offense (offense_no int NOT NULL, statute_cite text NOT NULL, offense_date date NOT NULL, CONSTRAINT offense_pkey PRIMARY KEY (offense_no));
+}
+
+teardown
+{
+ DROP TABLE statute, offense;
+}
+
+session "s1"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "rx1" { SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15'); }
+step "wy1" { INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15'); }
+step "c1" { COMMIT; }
+
+session "s2"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "ry2" { SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01'; }
+step "wx2" { DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01'; }
+step "c2" { COMMIT; }
diff --git a/src/test/isolation/specs/total-cash.spec b/src/test/isolation/specs/total-cash.spec
new file mode 100644
index 00000000000..843f41c77f2
--- /dev/null
+++ b/src/test/isolation/specs/total-cash.spec
@@ -0,0 +1,28 @@
+# Total Cash test
+#
+# Another famous test of snapshot isolation anomaly.
+#
+# Any overlap between the transactions must cause a serialization failure.
+
+setup
+{
+ CREATE TABLE accounts (accountid text NOT NULL PRIMARY KEY, balance numeric not null);
+ INSERT INTO accounts VALUES ('checking', 600),('savings',600);
+}
+
+teardown
+{
+ DROP TABLE accounts;
+}
+
+session "s1"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "wx1" { UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking'; }
+step "rxy1" { SELECT SUM(balance) FROM accounts; }
+step "c1" { COMMIT; }
+
+session "s2"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "wy2" { UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings'; }
+step "rxy2" { SELECT SUM(balance) FROM accounts; }
+step "c2" { COMMIT; }
diff --git a/src/test/isolation/specs/two-ids.spec b/src/test/isolation/specs/two-ids.spec
new file mode 100644
index 00000000000..d67064068e0
--- /dev/null
+++ b/src/test/isolation/specs/two-ids.spec
@@ -0,0 +1,40 @@
+# Two IDs test
+#
+# Small, simple test showing read-only anomalies.
+#
+# There are only four permuations which must cause a serialization failure.
+# Required failure cases are where s2 overlaps both s1 and s3, but s1
+# commits before s3 executes its first SELECT.
+#
+# If s3 were declared READ ONLY there would be no false positives.
+# With s3 defaulting to READ WRITE, we currently expect 12 false
+# positives. Further work dealing with de facto READ ONLY transactions
+# may be able to reduce or eliminate those false positives.
+
+setup
+{
+ create table D1 (id int not null);
+ create table D2 (id int not null);
+ insert into D1 values (1);
+ insert into D2 values (1);
+}
+
+teardown
+{
+ DROP TABLE D1, D2;
+}
+
+session "s1"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "wx1" { update D1 set id = id + 1; }
+step "c1" { COMMIT; }
+
+session "s2"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "rxwy2" { update D2 set id = (select id+1 from D1); }
+step "c2" { COMMIT; }
+
+session "s3"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "ry3" { select id from D2; }
+step "c3" { COMMIT; }
diff --git a/src/test/isolation/specscanner.l b/src/test/isolation/specscanner.l
new file mode 100644
index 00000000000..6752aca82d4
--- /dev/null
+++ b/src/test/isolation/specscanner.l
@@ -0,0 +1,103 @@
+%{
+/*-------------------------------------------------------------------------
+ *
+ * specscanner.l
+ * a lexical scanner for an isolation test specification
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *-------------------------------------------------------------------------
+ */
+
+static int yyline = 1; /* line number for error reporting */
+
+static char litbuf[1024];
+static int litbufpos = 0;
+
+static void addlitchar(const char c);
+
+%}
+
+%option 8bit
+%option never-interactive
+%option nodefault
+%option noinput
+%option nounput
+%option noyywrap
+%option prefix="spec_yy"
+
+
+%x sql
+%x qstr
+
+non_newline [^\n\r]
+space [ \t\n\r\f]
+
+comment ("#"{non_newline}*)
+whitespace ({space}+|{comment})
+
+%%
+
+permutation { return(PERMUTATION); }
+session { return(SESSION); }
+setup { return(SETUP); }
+step { return(STEP); }
+teardown { return(TEARDOWN); }
+
+[\n] { yyline++; }
+{whitespace} {
+ /* ignore */
+ }
+
+\" {
+ litbufpos = 0;
+ BEGIN(qstr);
+ }
+<qstr>\" {
+ litbuf[litbufpos] = '\0';
+ yylval.str = strdup(litbuf);
+ BEGIN(INITIAL);
+ return(string);
+ }
+<qstr>. { addlitchar(yytext[0]); }
+
+"{" {
+
+ litbufpos = 0;
+ BEGIN(sql);
+ }
+
+<sql>"}" {
+ litbuf[litbufpos] = '\0';
+ yylval.str = strdup(litbuf);
+ BEGIN(INITIAL);
+ return(sqlblock);
+ }
+<sql>[^}] { addlitchar(yytext[0]);}
+
+
+. {
+ fprintf(stderr, "syntax error at line %d: unexpected character \"%s\"\n", yyline, yytext);
+ exit(1);
+ }
+
+%%
+
+static void
+addlitchar(const char c)
+{
+ if (litbufpos >= sizeof(litbuf) - 1)
+ {
+ fprintf(stderr, "SQL step too long\n");
+ exit(1);
+ }
+ litbuf[litbufpos++] = c;
+}
+
+void
+yyerror(const char *message)
+{
+ fprintf(stderr, "%s at line %d\n", message, yyline);
+ exit(1);
+}
diff --git a/src/test/regress/expected/prepared_xacts.out b/src/test/regress/expected/prepared_xacts.out
index 292962ab7b3..1a6b4ce1d94 100644
--- a/src/test/regress/expected/prepared_xacts.out
+++ b/src/test/regress/expected/prepared_xacts.out
@@ -9,7 +9,7 @@
CREATE TABLE pxtest1 (foobar VARCHAR(10));
INSERT INTO pxtest1 VALUES ('aaa');
-- Test PREPARE TRANSACTION
-BEGIN;
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
UPDATE pxtest1 SET foobar = 'bbb' WHERE foobar = 'aaa';
SELECT * FROM pxtest1;
foobar
@@ -45,7 +45,7 @@ SELECT gid FROM pg_prepared_xacts;
(0 rows)
-- Test COMMIT PREPARED
-BEGIN;
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
INSERT INTO pxtest1 VALUES ('ddd');
SELECT * FROM pxtest1;
foobar
@@ -70,7 +70,7 @@ SELECT * FROM pxtest1;
(2 rows)
-- Test duplicate gids
-BEGIN;
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
UPDATE pxtest1 SET foobar = 'eee' WHERE foobar = 'ddd';
SELECT * FROM pxtest1;
foobar
@@ -86,7 +86,7 @@ SELECT gid FROM pg_prepared_xacts;
foo3
(1 row)
-BEGIN;
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
INSERT INTO pxtest1 VALUES ('fff');
SELECT * FROM pxtest1;
foobar
@@ -117,7 +117,7 @@ SELECT * FROM pxtest1;
-- Clean up
DROP TABLE pxtest1;
-- Test subtransactions
-BEGIN;
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
CREATE TABLE pxtest2 (a int);
INSERT INTO pxtest2 VALUES (1);
SAVEPOINT a;
@@ -128,7 +128,7 @@ BEGIN;
PREPARE TRANSACTION 'regress-one';
CREATE TABLE pxtest3(fff int);
-- Test shared invalidation
-BEGIN;
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
DROP TABLE pxtest3;
CREATE TABLE pxtest4 (a int);
INSERT INTO pxtest4 VALUES (1);
diff --git a/src/test/regress/expected/prepared_xacts_1.out b/src/test/regress/expected/prepared_xacts_1.out
index 991a11bdfa9..5051eaabd71 100644
--- a/src/test/regress/expected/prepared_xacts_1.out
+++ b/src/test/regress/expected/prepared_xacts_1.out
@@ -9,7 +9,7 @@
CREATE TABLE pxtest1 (foobar VARCHAR(10));
INSERT INTO pxtest1 VALUES ('aaa');
-- Test PREPARE TRANSACTION
-BEGIN;
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
UPDATE pxtest1 SET foobar = 'bbb' WHERE foobar = 'aaa';
SELECT * FROM pxtest1;
foobar
@@ -47,7 +47,7 @@ SELECT gid FROM pg_prepared_xacts;
(0 rows)
-- Test COMMIT PREPARED
-BEGIN;
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
INSERT INTO pxtest1 VALUES ('ddd');
SELECT * FROM pxtest1;
foobar
@@ -74,7 +74,7 @@ SELECT * FROM pxtest1;
(1 row)
-- Test duplicate gids
-BEGIN;
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
UPDATE pxtest1 SET foobar = 'eee' WHERE foobar = 'ddd';
SELECT * FROM pxtest1;
foobar
@@ -90,7 +90,7 @@ SELECT gid FROM pg_prepared_xacts;
-----
(0 rows)
-BEGIN;
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
INSERT INTO pxtest1 VALUES ('fff');
SELECT * FROM pxtest1;
foobar
@@ -120,7 +120,7 @@ SELECT * FROM pxtest1;
-- Clean up
DROP TABLE pxtest1;
-- Test subtransactions
-BEGIN;
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
CREATE TABLE pxtest2 (a int);
INSERT INTO pxtest2 VALUES (1);
SAVEPOINT a;
@@ -133,7 +133,7 @@ ERROR: prepared transactions are disabled
HINT: Set max_prepared_transactions to a nonzero value.
CREATE TABLE pxtest3(fff int);
-- Test shared invalidation
-BEGIN;
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
DROP TABLE pxtest3;
CREATE TABLE pxtest4 (a int);
INSERT INTO pxtest4 VALUES (1);
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index b91c9d8e237..f49ec0effee 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -44,7 +44,7 @@ SELECT * FROM aggtest;
CREATE TABLE writetest (a int);
CREATE TEMPORARY TABLE temptest (a int);
BEGIN;
-SET TRANSACTION READ ONLY; -- ok
+SET TRANSACTION ISOLATION LEVEL SERIALIZABLE, READ ONLY, DEFERRABLE; -- ok
SELECT * FROM writetest; -- ok
a
---
diff --git a/src/test/regress/sql/prepared_xacts.sql b/src/test/regress/sql/prepared_xacts.sql
index 39d323a15b6..2bdbb0d1891 100644
--- a/src/test/regress/sql/prepared_xacts.sql
+++ b/src/test/regress/sql/prepared_xacts.sql
@@ -14,7 +14,7 @@ INSERT INTO pxtest1 VALUES ('aaa');
-- Test PREPARE TRANSACTION
-BEGIN;
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
UPDATE pxtest1 SET foobar = 'bbb' WHERE foobar = 'aaa';
SELECT * FROM pxtest1;
PREPARE TRANSACTION 'foo1';
@@ -33,7 +33,7 @@ SELECT gid FROM pg_prepared_xacts;
-- Test COMMIT PREPARED
-BEGIN;
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
INSERT INTO pxtest1 VALUES ('ddd');
SELECT * FROM pxtest1;
PREPARE TRANSACTION 'foo2';
@@ -45,14 +45,14 @@ COMMIT PREPARED 'foo2';
SELECT * FROM pxtest1;
-- Test duplicate gids
-BEGIN;
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
UPDATE pxtest1 SET foobar = 'eee' WHERE foobar = 'ddd';
SELECT * FROM pxtest1;
PREPARE TRANSACTION 'foo3';
SELECT gid FROM pg_prepared_xacts;
-BEGIN;
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
INSERT INTO pxtest1 VALUES ('fff');
SELECT * FROM pxtest1;
@@ -69,7 +69,7 @@ SELECT * FROM pxtest1;
DROP TABLE pxtest1;
-- Test subtransactions
-BEGIN;
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
CREATE TABLE pxtest2 (a int);
INSERT INTO pxtest2 VALUES (1);
SAVEPOINT a;
@@ -82,7 +82,7 @@ PREPARE TRANSACTION 'regress-one';
CREATE TABLE pxtest3(fff int);
-- Test shared invalidation
-BEGIN;
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
DROP TABLE pxtest3;
CREATE TABLE pxtest4 (a int);
INSERT INTO pxtest4 VALUES (1);
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 7c9638f05e2..23271c8eaba 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -40,7 +40,7 @@ CREATE TABLE writetest (a int);
CREATE TEMPORARY TABLE temptest (a int);
BEGIN;
-SET TRANSACTION READ ONLY; -- ok
+SET TRANSACTION ISOLATION LEVEL SERIALIZABLE, READ ONLY, DEFERRABLE; -- ok
SELECT * FROM writetest; -- ok
SET TRANSACTION READ WRITE; --fail
COMMIT;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 7b3787a66ec..b73262d8629 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -795,6 +795,7 @@ LINE
LOCALLOCK
LOCALLOCKOWNER
LOCALLOCKTAG
+LOCALPREDICATELOCK
LOCK
LOCKMASK
LOCKMETHODID
@@ -931,6 +932,7 @@ OffsetNumber
OffsetVarNodes_context
Oid
OidOptions
+OldSerXidControlData
OldToNewMapping
OldToNewMappingData
OldTriggerInfo
@@ -1081,6 +1083,10 @@ PQconninfoOption
PQnoticeProcessor
PQnoticeReceiver
PQprintOpt
+PREDICATELOCK
+PREDICATELOCKTAG
+PREDICATELOCKTARGET
+PREDICATELOCKTARGETTAG
PROC
PROCESS_INFORMATION
PROCLOCK
@@ -1202,6 +1208,9 @@ PreParseColumnRefHook
PredClass
PredIterInfo
PredIterInfoData
+PredXactListData
+PredXactListElementData
+PredicateLockData
PrepareStmt
PreparedParamsData
PreparedStatement
@@ -1268,6 +1277,8 @@ RSA
RTEKind
RUHashEntry
RUHashEntryData
+RWConflictData
+RWConflictPoolHeaderData
RangeFunction
RangeQueryClause
RangeSubselect
@@ -1350,6 +1361,10 @@ RunningTransactionsData
SC_HANDLE
SECURITY_ATTRIBUTES
SEG
+SERIALIZABLEXACT
+SERIALIZABLEXACTTAG
+SERIALIZABLEXID
+SERIALIZABLEXIDTAG
SERVICE_STATUS
SERVICE_STATUS_HANDLE
SERVICE_TABLE_ENTRY
@@ -1595,6 +1610,9 @@ TwoPhaseCallback
TwoPhaseFileHeader
TwoPhaseLockRecord
TwoPhasePgStatRecord
+TwoPhasePredicateLockRecord
+TwoPhasePredicateRecord
+TwoPhasePredicateXactRecord
TwoPhaseRecordOnDisk
TwoPhaseRmgrId
TwoPhaseStateData