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); +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'); +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); +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); +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); +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); +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"