diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index c740952..4593aee 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -126,7 +126,8 @@ brinhandler(PG_FUNCTION_ARGS)
 bool
 brininsert(Relation idxRel, Datum *values, bool *nulls,
 		   ItemPointer heaptid, Relation heapRel,
-		   IndexUniqueCheck checkUnique)
+		   IndexUniqueCheck checkUnique,
+		   Snapshot snapshot)
 {
 	BlockNumber pagesPerRange;
 	BrinDesc   *bdesc = NULL;
diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c
index cd21e0e..df09394 100644
--- a/src/backend/access/gin/gininsert.c
+++ b/src/backend/access/gin/gininsert.c
@@ -486,7 +486,8 @@ ginHeapTupleInsert(GinState *ginstate, OffsetNumber attnum,
 bool
 gininsert(Relation index, Datum *values, bool *isnull,
 		  ItemPointer ht_ctid, Relation heapRel,
-		  IndexUniqueCheck checkUnique)
+		  IndexUniqueCheck checkUnique,
+		  Snapshot snapshot)
 {
 	GinState	ginstate;
 	MemoryContext oldCtx;
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 996363c..1e54585 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -141,7 +141,8 @@ gistbuildempty(Relation index)
 bool
 gistinsert(Relation r, Datum *values, bool *isnull,
 		   ItemPointer ht_ctid, Relation heapRel,
-		   IndexUniqueCheck checkUnique)
+		   IndexUniqueCheck checkUnique,
+		   Snapshot snapshot)
 {
 	IndexTuple	itup;
 	GISTSTATE  *giststate;
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 3d48c4f..43339d0 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -206,7 +206,8 @@ hashbuildCallback(Relation index,
 bool
 hashinsert(Relation rel, Datum *values, bool *isnull,
 		   ItemPointer ht_ctid, Relation heapRel,
-		   IndexUniqueCheck checkUnique)
+		   IndexUniqueCheck checkUnique,
+		   Snapshot snapshot)
 {
 	IndexTuple	itup;
 
diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
index 4cffd21..aa4f205 100644
--- a/src/backend/access/heap/tuptoaster.c
+++ b/src/backend/access/heap/tuptoaster.c
@@ -1615,7 +1615,8 @@ toast_save_datum(Relation rel, Datum value,
 							 &(toasttup->t_self),
 							 toastrel,
 							 toastidxs[i]->rd_index->indisunique ?
-							 UNIQUE_CHECK_YES : UNIQUE_CHECK_NO);
+							 UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
+							 NULL);
 		}
 
 		/*
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index 54b71cb..12e5942 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -191,7 +191,8 @@ index_insert(Relation indexRelation,
 			 bool *isnull,
 			 ItemPointer heap_t_ctid,
 			 Relation heapRelation,
-			 IndexUniqueCheck checkUnique)
+			 IndexUniqueCheck checkUnique,
+			 Snapshot snapshot)
 {
 	RELATION_CHECKS;
 	CHECK_REL_PROCEDURE(aminsert);
@@ -203,7 +204,8 @@ index_insert(Relation indexRelation,
 
 	return indexRelation->rd_amroutine->aminsert(indexRelation, values, isnull,
 												 heap_t_ctid, heapRelation,
-												 checkUnique);
+												 checkUnique,
+												 snapshot);
 }
 
 /*
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index e3c55eb..9556edd 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -52,7 +52,8 @@ static TransactionId _bt_check_unique(Relation rel, IndexTuple itup,
 				 Relation heapRel, Buffer buf, OffsetNumber offset,
 				 ScanKey itup_scankey,
 				 IndexUniqueCheck checkUnique, bool *is_unique,
-				 uint32 *speculativeToken);
+				 uint32 *speculativeToken,
+				 Snapshot snapshot);
 static void _bt_findinsertloc(Relation rel,
 				  Buffer *bufptr,
 				  OffsetNumber *offsetptr,
@@ -105,7 +106,8 @@ static void _bt_vacuum_one_page(Relation rel, Buffer buffer, Relation heapRel);
  */
 bool
 _bt_doinsert(Relation rel, IndexTuple itup,
-			 IndexUniqueCheck checkUnique, Relation heapRel)
+			 IndexUniqueCheck checkUnique, Relation heapRel,
+			 Snapshot snapshot)
 {
 	bool		is_unique = false;
 	int			natts = rel->rd_rel->relnatts;
@@ -165,7 +167,8 @@ top:
 
 		offset = _bt_binsrch(rel, buf, natts, itup_scankey, false);
 		xwait = _bt_check_unique(rel, itup, heapRel, buf, offset, itup_scankey,
-								 checkUnique, &is_unique, &speculativeToken);
+								 checkUnique, &is_unique, &speculativeToken,
+								 snapshot);
 
 		if (TransactionIdIsValid(xwait))
 		{
@@ -188,6 +191,13 @@ top:
 		}
 	}
 
+	/*
+	 * By determining that there is no duplicate key, we have effectively
+	 * read this index.  We predicate-lock the index page so we can detect
+	 * conflicting read-write sequences.
+	 */
+	PredicateLockPage(rel, buf, snapshot);
+
 	if (checkUnique != UNIQUE_CHECK_EXISTING)
 	{
 		/*
@@ -217,6 +227,46 @@ top:
 }
 
 /*
+ * Check if a heap tuple is still live, or committed dead.  If the tuple is
+ * live and snapshot is not NULL, also check for serialization conflicts.
+ */
+static bool
+check_unique_tuple_still_live(Relation irel, Buffer ibuf, Relation heapRel,
+							  ItemPointer htid, Snapshot snapshot)
+{
+	Buffer heapBuf;
+	HeapTupleData heapTuple;
+	bool visible;
+	bool found;
+
+	heapBuf = ReadBuffer(heapRel,
+						 ItemPointerGetBlockNumber(htid));
+	LockBuffer(heapBuf, BUFFER_LOCK_SHARE);
+	found = heap_hot_search_buffer(htid, heapRel, heapBuf,
+								   SnapshotSelf, &heapTuple,
+								   NULL, true);
+	if (found && snapshot != NULL)
+	{
+		/*
+		 * The caller will ereport a unique constraint violation when we
+		 * return.  Before we do that, give SSI a chance to detect a
+		 * serialization failure.
+		 */
+		CheckForSerializableConflictIn(irel, NULL, ibuf);
+		visible = HeapTupleSatisfiesMVCC(&heapTuple,
+										 snapshot,
+										 heapBuf);
+		CheckForSerializableConflictOut(visible, heapRel,
+										&heapTuple,
+										heapBuf, snapshot);
+	}
+	LockBuffer(heapBuf, BUFFER_LOCK_UNLOCK);
+	ReleaseBuffer(heapBuf);
+
+	return found;
+}
+
+/*
  *	_bt_check_unique() -- Check for violation of unique index constraint
  *
  * offset points to the first possible item that could conflict. It can
@@ -239,7 +289,8 @@ static TransactionId
 _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
 				 Buffer buf, OffsetNumber offset, ScanKey itup_scankey,
 				 IndexUniqueCheck checkUnique, bool *is_unique,
-				 uint32 *speculativeToken)
+				 uint32 *speculativeToken,
+				 Snapshot snapshot)
 {
 	TupleDesc	itupdesc = RelationGetDescr(rel);
 	int			natts = rel->rd_rel->relnatts;
@@ -378,7 +429,8 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
 					 * entry.
 					 */
 					htid = itup->t_tid;
-					if (heap_hot_search(&htid, heapRel, SnapshotSelf, NULL))
+					if (check_unique_tuple_still_live(rel, buf, heapRel, &htid,
+													  snapshot))
 					{
 						/* Normal case --- it's still live */
 					}
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index f2905cb..09406a9 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -266,7 +266,8 @@ btbuildempty(Relation index)
 bool
 btinsert(Relation rel, Datum *values, bool *isnull,
 		 ItemPointer ht_ctid, Relation heapRel,
-		 IndexUniqueCheck checkUnique)
+		 IndexUniqueCheck checkUnique,
+		 Snapshot snapshot)
 {
 	bool		result;
 	IndexTuple	itup;
@@ -275,7 +276,7 @@ btinsert(Relation rel, Datum *values, bool *isnull,
 	itup = index_form_tuple(RelationGetDescr(rel), values, isnull);
 	itup->t_tid = *ht_ctid;
 
-	result = _bt_doinsert(rel, itup, checkUnique, heapRel);
+	result = _bt_doinsert(rel, itup, checkUnique, heapRel, snapshot);
 
 	pfree(itup);
 
diff --git a/src/backend/access/spgist/spginsert.c b/src/backend/access/spgist/spginsert.c
index 44fd644..c418873 100644
--- a/src/backend/access/spgist/spginsert.c
+++ b/src/backend/access/spgist/spginsert.c
@@ -205,7 +205,8 @@ spgbuildempty(Relation index)
 bool
 spginsert(Relation index, Datum *values, bool *isnull,
 		  ItemPointer ht_ctid, Relation heapRel,
-		  IndexUniqueCheck checkUnique)
+		  IndexUniqueCheck checkUnique,
+		  Snapshot snapshot)
 {
 	SpGistState spgstate;
 	MemoryContext oldCtx;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 313ee9c..db8b48e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3139,7 +3139,8 @@ validate_index_heapscan(Relation heapRelation,
 						 &rootTuple,
 						 heapRelation,
 						 indexInfo->ii_Unique ?
-						 UNIQUE_CHECK_YES : UNIQUE_CHECK_NO);
+						 UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
+						 NULL);
 
 			state->tups_inserted += 1;
 		}
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index b9fe102..967abf2 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -139,7 +139,8 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
 					 &(heapTuple->t_self),		/* tid of heap tuple */
 					 heapRelation,
 					 relationDescs[i]->rd_index->indisunique ?
-					 UNIQUE_CHECK_YES : UNIQUE_CHECK_NO);
+					 UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
+					 NULL);
 	}
 
 	ExecDropSingleTupleTableSlot(slot);
diff --git a/src/backend/commands/constraint.c b/src/backend/commands/constraint.c
index 26f9114..a30d4b4 100644
--- a/src/backend/commands/constraint.c
+++ b/src/backend/commands/constraint.c
@@ -165,7 +165,8 @@ unique_key_recheck(PG_FUNCTION_ARGS)
 		 * index will know about.
 		 */
 		index_insert(indexRel, values, isnull, &(new_row->t_self),
-					 trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
+					 trigdata->tg_relation, UNIQUE_CHECK_EXISTING,
+					 NULL);
 	}
 	else
 	{
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 838cee7..b259d74 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -386,7 +386,8 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
 						 isnull,	/* null flags */
 						 tupleid,		/* tid of heap tuple */
 						 heapRelation,	/* heap relation */
-						 checkUnique);	/* type of uniqueness check to do */
+						 checkUnique,	/* type of uniqueness check to do */
+						 estate->es_snapshot);
 
 		/*
 		 * If the index has an associated exclusion constraint, check that.
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index 35f1061..5a1a62f 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -44,7 +44,8 @@ typedef bool (*aminsert_function) (Relation indexRelation,
 											   bool *isnull,
 											   ItemPointer heap_tid,
 											   Relation heapRelation,
-											   IndexUniqueCheck checkUnique);
+											   IndexUniqueCheck checkUnique,
+											   Snapshot snapshot);
 
 /* bulk delete */
 typedef IndexBulkDeleteResult *(*ambulkdelete_function) (IndexVacuumInfo *info,
diff --git a/src/include/access/brin_internal.h b/src/include/access/brin_internal.h
index 47317af..058ad78 100644
--- a/src/include/access/brin_internal.h
+++ b/src/include/access/brin_internal.h
@@ -89,7 +89,8 @@ extern IndexBuildResult *brinbuild(Relation heap, Relation index,
 extern void brinbuildempty(Relation index);
 extern bool brininsert(Relation idxRel, Datum *values, bool *nulls,
 		   ItemPointer heaptid, Relation heapRel,
-		   IndexUniqueCheck checkUnique);
+		   IndexUniqueCheck checkUnique,
+		   Snapshot snapshot);
 extern IndexScanDesc brinbeginscan(Relation r, int nkeys, int norderbys);
 extern int64 bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm);
 extern void brinrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index 81907d5..5d8ab60 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -129,7 +129,8 @@ extern bool index_insert(Relation indexRelation,
 			 Datum *values, bool *isnull,
 			 ItemPointer heap_t_ctid,
 			 Relation heapRelation,
-			 IndexUniqueCheck checkUnique);
+			 IndexUniqueCheck checkUnique,
+			 Snapshot snapshot);
 
 extern IndexScanDesc index_beginscan(Relation heapRelation,
 				Relation indexRelation,
diff --git a/src/include/access/gin_private.h b/src/include/access/gin_private.h
index d2ea588..c96dfc9 100644
--- a/src/include/access/gin_private.h
+++ b/src/include/access/gin_private.h
@@ -620,7 +620,8 @@ extern IndexBuildResult *ginbuild(Relation heap, Relation index,
 extern void ginbuildempty(Relation index);
 extern bool gininsert(Relation index, Datum *values, bool *isnull,
 		  ItemPointer ht_ctid, Relation heapRel,
-		  IndexUniqueCheck checkUnique);
+		  IndexUniqueCheck checkUnique,
+		  Snapshot snapshot);
 extern void ginEntryInsert(GinState *ginstate,
 			   OffsetNumber attnum, Datum key, GinNullCategory category,
 			   ItemPointerData *items, uint32 nitem,
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index f9732ba..0d12d47 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -431,7 +431,8 @@ extern Datum gisthandler(PG_FUNCTION_ARGS);
 extern void gistbuildempty(Relation index);
 extern bool gistinsert(Relation r, Datum *values, bool *isnull,
 		   ItemPointer ht_ctid, Relation heapRel,
-		   IndexUniqueCheck checkUnique);
+		   IndexUniqueCheck checkUnique,
+		   Snapshot snapshot);
 extern MemoryContext createTempGistContext(void);
 extern GISTSTATE *initGISTstate(Relation index);
 extern void freeGISTstate(GISTSTATE *giststate);
diff --git a/src/include/access/hash.h b/src/include/access/hash.h
index 3a68390..946c4a8 100644
--- a/src/include/access/hash.h
+++ b/src/include/access/hash.h
@@ -249,7 +249,8 @@ extern IndexBuildResult *hashbuild(Relation heap, Relation index,
 extern void hashbuildempty(Relation index);
 extern bool hashinsert(Relation rel, Datum *values, bool *isnull,
 		   ItemPointer ht_ctid, Relation heapRel,
-		   IndexUniqueCheck checkUnique);
+		   IndexUniqueCheck checkUnique,
+		   Snapshot snapshot);
 extern bool hashgettuple(IndexScanDesc scan, ScanDirection dir);
 extern int64 hashgetbitmap(IndexScanDesc scan, TIDBitmap *tbm);
 extern IndexScanDesc hashbeginscan(Relation rel, int nkeys, int norderbys);
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 06822fa..2ac507a 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -660,7 +660,8 @@ extern IndexBuildResult *btbuild(Relation heap, Relation index,
 extern void btbuildempty(Relation index);
 extern bool btinsert(Relation rel, Datum *values, bool *isnull,
 		 ItemPointer ht_ctid, Relation heapRel,
-		 IndexUniqueCheck checkUnique);
+		 IndexUniqueCheck checkUnique,
+		 Snapshot snapshot);
 extern IndexScanDesc btbeginscan(Relation rel, int nkeys, int norderbys);
 extern bool btgettuple(IndexScanDesc scan, ScanDirection dir);
 extern int64 btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm);
@@ -681,7 +682,8 @@ extern bool btcanreturn(Relation index, int attno);
  * prototypes for functions in nbtinsert.c
  */
 extern bool _bt_doinsert(Relation rel, IndexTuple itup,
-			 IndexUniqueCheck checkUnique, Relation heapRel);
+			 IndexUniqueCheck checkUnique, Relation heapRel,
+			 Snapshot snapshot);
 extern Buffer _bt_getstackbuf(Relation rel, BTStack stack, int access);
 extern void _bt_finish_split(Relation rel, Buffer bbuf, BTStack stack);
 
diff --git a/src/include/access/spgist.h b/src/include/access/spgist.h
index 1994f71..f201f7b 100644
--- a/src/include/access/spgist.h
+++ b/src/include/access/spgist.h
@@ -184,7 +184,8 @@ extern IndexBuildResult *spgbuild(Relation heap, Relation index,
 extern void spgbuildempty(Relation index);
 extern bool spginsert(Relation index, Datum *values, bool *isnull,
 		  ItemPointer ht_ctid, Relation heapRel,
-		  IndexUniqueCheck checkUnique);
+		  IndexUniqueCheck checkUnique,
+		  Snapshot snapshot);
 
 /* spgscan.c */
 extern IndexScanDesc spgbeginscan(Relation rel, int keysz, int orderbysz);
diff --git a/src/test/isolation/expected/read-write-unique-2.out b/src/test/isolation/expected/read-write-unique-2.out
new file mode 100644
index 0000000..5e27f0a
--- /dev/null
+++ b/src/test/isolation/expected/read-write-unique-2.out
@@ -0,0 +1,29 @@
+Parsed test spec with 2 sessions
+
+starting permutation: r1 r2 w1 w2 c1 c2
+step r1: SELECT * FROM test WHERE i = 42;
+i              
+
+step r2: SELECT * FROM test WHERE i = 42;
+i              
+
+step w1: INSERT INTO test VALUES (42);
+step w2: INSERT INTO test VALUES (42); <waiting ...>
+step c1: COMMIT;
+step w2: <... completed>
+error in steps c1 w2: ERROR:  could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: r1 w1 c1 r2 w2 c2
+step r1: SELECT * FROM test WHERE i = 42;
+i              
+
+step w1: INSERT INTO test VALUES (42);
+step c1: COMMIT;
+step r2: SELECT * FROM test WHERE i = 42;
+i              
+
+42             
+step w2: INSERT INTO test VALUES (42);
+ERROR:  duplicate key value violates unique constraint "test_pkey"
+step c2: COMMIT;
diff --git a/src/test/isolation/expected/read-write-unique-3.out b/src/test/isolation/expected/read-write-unique-3.out
new file mode 100644
index 0000000..edd3558
--- /dev/null
+++ b/src/test/isolation/expected/read-write-unique-3.out
@@ -0,0 +1,12 @@
+Parsed test spec with 2 sessions
+
+starting permutation: rw1 rw2 c1 c2
+step rw1: SELECT insert_unique(1, '1');
+insert_unique  
+
+               
+step rw2: SELECT insert_unique(1, '2'); <waiting ...>
+step c1: COMMIT;
+step rw2: <... completed>
+error in steps c1 rw2: ERROR:  could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
diff --git a/src/test/isolation/expected/read-write-unique-4.out b/src/test/isolation/expected/read-write-unique-4.out
new file mode 100644
index 0000000..64ff157
--- /dev/null
+++ b/src/test/isolation/expected/read-write-unique-4.out
@@ -0,0 +1,41 @@
+Parsed test spec with 2 sessions
+
+starting permutation: r1 r2 w1 w2 c1 c2
+step r1: SELECT COALESCE(MAX(invoice_number) + 1, 1) FROM invoice WHERE year = 2016;
+coalesce       
+
+3              
+step r2: SELECT COALESCE(MAX(invoice_number) + 1, 1) FROM invoice WHERE year = 2016;
+coalesce       
+
+3              
+step w1: INSERT INTO invoice VALUES (2016, 3);
+step w2: INSERT INTO invoice VALUES (2016, 3); <waiting ...>
+step c1: COMMIT;
+step w2: <... completed>
+error in steps c1 w2: ERROR:  could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: r1 w1 w2 c1 c2
+step r1: SELECT COALESCE(MAX(invoice_number) + 1, 1) FROM invoice WHERE year = 2016;
+coalesce       
+
+3              
+step w1: INSERT INTO invoice VALUES (2016, 3);
+step w2: INSERT INTO invoice VALUES (2016, 3); <waiting ...>
+step c1: COMMIT;
+step w2: <... completed>
+error in steps c1 w2: ERROR:  duplicate key value violates unique constraint "invoice_pkey"
+step c2: COMMIT;
+
+starting permutation: r2 w1 w2 c1 c2
+step r2: SELECT COALESCE(MAX(invoice_number) + 1, 1) FROM invoice WHERE year = 2016;
+coalesce       
+
+3              
+step w1: INSERT INTO invoice VALUES (2016, 3);
+step w2: INSERT INTO invoice VALUES (2016, 3); <waiting ...>
+step c1: COMMIT;
+step w2: <... completed>
+error in steps c1 w2: ERROR:  duplicate key value violates unique constraint "invoice_pkey"
+step c2: COMMIT;
diff --git a/src/test/isolation/expected/read-write-unique.out b/src/test/isolation/expected/read-write-unique.out
new file mode 100644
index 0000000..fb32ec3
--- /dev/null
+++ b/src/test/isolation/expected/read-write-unique.out
@@ -0,0 +1,29 @@
+Parsed test spec with 2 sessions
+
+starting permutation: r1 r2 w1 w2 c1 c2
+step r1: SELECT * FROM test;
+i              
+
+step r2: SELECT * FROM test;
+i              
+
+step w1: INSERT INTO test VALUES (42);
+step w2: INSERT INTO test VALUES (42); <waiting ...>
+step c1: COMMIT;
+step w2: <... completed>
+error in steps c1 w2: ERROR:  could not serialize access due to read/write dependencies among transactions
+step c2: COMMIT;
+
+starting permutation: r1 w1 c1 r2 w2 c2
+step r1: SELECT * FROM test;
+i              
+
+step w1: INSERT INTO test VALUES (42);
+step c1: COMMIT;
+step r2: SELECT * FROM test;
+i              
+
+42             
+step w2: INSERT INTO test VALUES (42);
+ERROR:  duplicate key value violates unique constraint "test_pkey"
+step c2: COMMIT;
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 09dc9d4..317f1b3 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -1,3 +1,7 @@
+test: read-write-unique
+test: read-write-unique-2
+test: read-write-unique-3
+test: read-write-unique-4
 test: simple-write-skew
 test: receipt-report
 test: temporal-range-integrity
diff --git a/src/test/isolation/specs/read-write-unique-2.spec b/src/test/isolation/specs/read-write-unique-2.spec
new file mode 100644
index 0000000..5e7cbf2
--- /dev/null
+++ b/src/test/isolation/specs/read-write-unique-2.spec
@@ -0,0 +1,36 @@
+# Read-write-unique test.
+
+setup
+{
+  CREATE TABLE test (i integer PRIMARY KEY);
+}
+
+teardown
+{
+  DROP TABLE test;
+}
+
+session "s1"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "r1" { SELECT * FROM test WHERE i = 42; }
+step "w1" { INSERT INTO test VALUES (42); }
+step "c1" { COMMIT; }
+
+session "s2"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "r2" { SELECT * FROM test WHERE i = 42; }
+step "w2" { INSERT INTO test VALUES (42); }
+step "c2" { COMMIT; }
+
+# Two SSI transactions see that there is no row with value 42
+# in the table, then try to insert that value; T1 inserts,
+# and then T2 blocks waiting for T1 to commit.  Finally,
+# T2 reports a serialization failure.
+
+permutation "r1" "r2" "w1" "w2" "c1" "c2"
+
+# If the value is already visible before T2 begins, then a
+# regular unique constraint violation should still be raised
+# by T2.
+
+permutation "r1" "w1" "c1" "r2" "w2" "c2"
diff --git a/src/test/isolation/specs/read-write-unique-3.spec b/src/test/isolation/specs/read-write-unique-3.spec
new file mode 100644
index 0000000..52d2877
--- /dev/null
+++ b/src/test/isolation/specs/read-write-unique-3.spec
@@ -0,0 +1,33 @@
+# Read-write-unique test.
+# From bug report 9301.
+
+setup
+{
+  CREATE TABLE test (
+    key   integer UNIQUE,
+    val   text
+  );
+
+  CREATE OR REPLACE FUNCTION insert_unique(k integer, v text) RETURNS void
+  LANGUAGE SQL AS $$
+    INSERT INTO test (key, val) SELECT k, v WHERE NOT EXISTS (SELECT key FROM test WHERE key = k);
+  $$;
+}
+
+teardown
+{
+  DROP FUNCTION insert_unique(integer, text);
+  DROP TABLE test;
+}
+
+session "s1"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "rw1" { SELECT insert_unique(1, '1'); }
+step "c1" { COMMIT; }
+
+session "s2"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "rw2" { SELECT insert_unique(1, '2'); }
+step "c2" { COMMIT; }
+
+permutation "rw1" "rw2" "c1" "c2"
diff --git a/src/test/isolation/specs/read-write-unique-4.spec b/src/test/isolation/specs/read-write-unique-4.spec
new file mode 100644
index 0000000..e535610
--- /dev/null
+++ b/src/test/isolation/specs/read-write-unique-4.spec
@@ -0,0 +1,40 @@
+# Read-write-unique test.
+# Implementing a gapless sequence of ID numbers for each year.
+
+setup
+{
+  CREATE TABLE invoice (
+    year int,
+    invoice_number int,
+    PRIMARY KEY (year, invoice_number)
+  );
+
+  INSERT INTO invoice VALUES (2016, 1), (2016, 2);
+}
+
+teardown
+{
+  DROP TABLE invoice;
+}
+
+session "s1"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "r1" { SELECT COALESCE(MAX(invoice_number) + 1, 1) FROM invoice WHERE year = 2016; }
+step "w1" { INSERT INTO invoice VALUES (2016, 3); }
+step "c1" { COMMIT; }
+
+session "s2"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "r2" { SELECT COALESCE(MAX(invoice_number) + 1, 1) FROM invoice WHERE year = 2016; }
+step "w2" { INSERT INTO invoice VALUES (2016, 3); }
+step "c2" { COMMIT; }
+
+# if they both read first then there should be an SSI conflict
+permutation "r1" "r2" "w1" "w2" "c1" "c2"
+
+# if s2 doesn't both to read first, then inserting 3 should generate a unique constraint failure
+permutation "r1" "w1" "w2" "c1" "c2"
+
+# if s1 doesn't both to read first, but s2 does, then s1 got lucky and s2 should still experience an SSI failure
+# TODO: but in fact it still experiences a unique constraint violation...
+permutation "r2" "w1" "w2" "c1" "c2"
diff --git a/src/test/isolation/specs/read-write-unique.spec b/src/test/isolation/specs/read-write-unique.spec
new file mode 100644
index 0000000..c782f10
--- /dev/null
+++ b/src/test/isolation/specs/read-write-unique.spec
@@ -0,0 +1,39 @@
+# Read-write-unique test.
+
+setup
+{
+  CREATE TABLE test (i integer PRIMARY KEY);
+}
+
+teardown
+{
+  DROP TABLE test;
+}
+
+session "s1"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "r1" { SELECT * FROM test; }
+step "w1" { INSERT INTO test VALUES (42); }
+step "c1" { COMMIT; }
+
+session "s2"
+setup { BEGIN ISOLATION LEVEL SERIALIZABLE; }
+step "r2" { SELECT * FROM test; }
+step "w2" { INSERT INTO test VALUES (42); }
+step "c2" { COMMIT; }
+
+# Two SSI transactions see that there is no row with value 42
+# in the table, then try to insert that value; T1 inserts,
+# and then T2 blocks waiting for T1 to commit.  Finally,
+# T2 reports a serialization failure.
+#
+# (In an earlier version of Postgres, T2 would report a unique
+# constraint violation).
+
+permutation "r1" "r2" "w1" "w2" "c1" "c2"
+
+# If the value is already visible before T2 begins, then a
+# regular unique constraint violation should still be raised
+# by T2.
+
+permutation "r1" "w1" "c1" "r2" "w2" "c2"
