diff --git a/src/test/isolation/README b/src/test/isolation/README index 087ac279c7..58fe50e58a 100644 --- a/src/test/isolation/README +++ b/src/test/isolation/README @@ -63,3 +63,16 @@ permutation "" ... all possible overlapping orderings of the given sessions. Lines beginning with a # are considered comments. + + +Support for blocking commands +============================= + +Each spec may contain commands that block until further action has been taken +(most likely, some other session runs a step that unblocks it or causes a +deadlock). Such a spec needs to be careful to manually specify valid +permutations, i.e. those that would not expect a blocked session to execute a +command. If the spec fails to follow that rule, the spec is aborted. + +Only one command can be waiting at a time. As long as one command is waiting, +other commands are run to completion synchronously. diff --git a/src/test/isolation/expected/fk-contention.out b/src/test/isolation/expected/fk-contention.out new file mode 100644 index 0000000000..61e84d158a --- /dev/null +++ b/src/test/isolation/expected/fk-contention.out @@ -0,0 +1,17 @@ +Parsed test spec with 2 sessions + +starting permutation: ins com upd +step ins: INSERT INTO bar VALUES (42); +step com: COMMIT; +step upd: UPDATE foo SET b = 'Hello World'; + +starting permutation: ins upd com +step ins: INSERT INTO bar VALUES (42); +step upd: UPDATE foo SET b = 'Hello World'; +step com: COMMIT; +step upd: <... completed> + +starting permutation: upd ins com +step upd: UPDATE foo SET b = 'Hello World'; +step ins: INSERT INTO bar VALUES (42); +step com: COMMIT; diff --git a/src/test/isolation/expected/fk-deadlock.out b/src/test/isolation/expected/fk-deadlock.out new file mode 100644 index 0000000000..6b6ee163c7 --- /dev/null +++ b/src/test/isolation/expected/fk-deadlock.out @@ -0,0 +1,67 @@ +Parsed test spec with 2 sessions + +starting permutation: s1i s1u s1c s2i s2u s2c +step s1i: INSERT INTO child VALUES (1, 1); +step s1u: UPDATE parent SET aux = 'bar'; +step s1c: COMMIT; +step s2i: INSERT INTO child VALUES (2, 1); +step s2u: UPDATE parent SET aux = 'baz'; +step s2c: COMMIT; + +starting permutation: s1i s1u s2i s1c s2u s2c +step s1i: INSERT INTO child VALUES (1, 1); +step s1u: UPDATE parent SET aux = 'bar'; +step s2i: INSERT INTO child VALUES (2, 1); +step s1c: COMMIT; +step s2i: <... completed> +step s2u: UPDATE parent SET aux = 'baz'; +step s2c: COMMIT; + +starting permutation: s1i s2i s1u s2u s1c s2c +step s1i: INSERT INTO child VALUES (1, 1); +step s2i: INSERT INTO child VALUES (2, 1); +step s1u: UPDATE parent SET aux = 'bar'; +step s2u: UPDATE parent SET aux = 'baz'; +step s1u: <... completed> +ERROR: deadlock detected +step s1c: COMMIT; +step s2c: COMMIT; + +starting permutation: s1i s2i s2u s1u s2c s1c +step s1i: INSERT INTO child VALUES (1, 1); +step s2i: INSERT INTO child VALUES (2, 1); +step s2u: UPDATE parent SET aux = 'baz'; +step s1u: UPDATE parent SET aux = 'bar'; +step s2u: <... completed> +ERROR: deadlock detected +step s2c: COMMIT; +step s1c: COMMIT; + +starting permutation: s2i s1i s1u s2u s1c s2c +step s2i: INSERT INTO child VALUES (2, 1); +step s1i: INSERT INTO child VALUES (1, 1); +step s1u: UPDATE parent SET aux = 'bar'; +step s2u: UPDATE parent SET aux = 'baz'; +step s1u: <... completed> +ERROR: deadlock detected +step s1c: COMMIT; +step s2c: COMMIT; + +starting permutation: s2i s1i s2u s1u s2c s1c +step s2i: INSERT INTO child VALUES (2, 1); +step s1i: INSERT INTO child VALUES (1, 1); +step s2u: UPDATE parent SET aux = 'baz'; +step s1u: UPDATE parent SET aux = 'bar'; +step s2u: <... completed> +ERROR: deadlock detected +step s2c: COMMIT; +step s1c: COMMIT; + +starting permutation: s2i s2u s1i s2c s1u s1c +step s2i: INSERT INTO child VALUES (2, 1); +step s2u: UPDATE parent SET aux = 'baz'; +step s1i: INSERT INTO child VALUES (1, 1); +step s2c: COMMIT; +step s1i: <... completed> +step s1u: UPDATE parent SET aux = 'bar'; +step s1c: COMMIT; diff --git a/src/test/isolation/expected/fk-deadlock2.out b/src/test/isolation/expected/fk-deadlock2.out new file mode 100644 index 0000000000..af3ce8ecfd --- /dev/null +++ b/src/test/isolation/expected/fk-deadlock2.out @@ -0,0 +1,107 @@ +Parsed test spec with 2 sessions + +starting permutation: s1u1 s1u2 s1c s2u1 s2u2 s2c +step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1; +step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s1c: COMMIT; +step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s2c: COMMIT; + +starting permutation: s1u1 s1u2 s2u1 s1c s2u2 s2c +step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1; +step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s1c: COMMIT; +step s2u1: <... completed> +step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s2c: COMMIT; + +starting permutation: s1u1 s2u1 s1u2 s2u2 s1c s2c +step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1; +step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s1u2: <... completed> +ERROR: deadlock detected +step s1c: COMMIT; +step s2c: COMMIT; + +starting permutation: s1u1 s2u1 s1u2 s2u2 s2c s1c +step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1; +step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s1u2: <... completed> +ERROR: deadlock detected +step s2c: COMMIT; +step s1c: COMMIT; + +starting permutation: s1u1 s2u1 s2u2 s1u2 s1c s2c +step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1; +step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s2u2: <... completed> +ERROR: deadlock detected +step s1c: COMMIT; +step s2c: COMMIT; + +starting permutation: s1u1 s2u1 s2u2 s1u2 s2c s1c +step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1; +step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s2u2: <... completed> +ERROR: deadlock detected +step s2c: COMMIT; +step s1c: COMMIT; + +starting permutation: s2u1 s1u1 s1u2 s2u2 s1c s2c +step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1; +step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s1u2: <... completed> +ERROR: deadlock detected +step s1c: COMMIT; +step s2c: COMMIT; + +starting permutation: s2u1 s1u1 s1u2 s2u2 s2c s1c +step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1; +step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s1u2: <... completed> +ERROR: deadlock detected +step s2c: COMMIT; +step s1c: COMMIT; + +starting permutation: s2u1 s1u1 s2u2 s1u2 s1c s2c +step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1; +step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s2u2: <... completed> +ERROR: deadlock detected +step s1c: COMMIT; +step s2c: COMMIT; + +starting permutation: s2u1 s1u1 s2u2 s1u2 s2c s1c +step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1; +step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s2u2: <... completed> +ERROR: deadlock detected +step s2c: COMMIT; +step s1c: COMMIT; + +starting permutation: s2u1 s2u2 s1u1 s2c s1u2 s1c +step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1; +step s2c: COMMIT; +step s1u1: <... completed> +step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; +step s1c: COMMIT; diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule index 6ea8a29f49..dc154735ea 100644 --- a/src/test/isolation/isolation_schedule +++ b/src/test/isolation/isolation_schedule @@ -9,3 +9,6 @@ test: ri-trigger test: partial-index test: two-ids test: multiple-row-versions +test: fk-contention +test: fk-deadlock +test: fk-deadlock2 diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 0f77917fb5..126e1856f0 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -11,18 +11,39 @@ #include #endif +#ifndef WIN32 +#include +#include +#endif /* ! WIN32 */ + +#ifdef HAVE_SYS_SELECT_H +#include +#endif + #include "libpq-fe.h" #include "isolationtester.h" +#define PREP_WAITING "isolationtester_waiting" + +/* + * conns[0] is the global setup, teardown, and watchdog connection. Additional + * connections represent spec-defined sessions. + */ static PGconn **conns = NULL; +static const char **backend_ids = 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_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); +#define STEP_NONBLOCK 0x1 /* return 0 as soon as cmd waits for a lock */ +#define STEP_RETRY 0x2 /* this is a retry of a previously-waiting cmd */ +static bool try_complete_step(Step *step, int flags); + static int step_qsort_cmp(const void *a, const void *b); static int step_bsearch_cmp(const void *a, const void *b); @@ -45,6 +66,7 @@ main(int argc, char **argv) const char *conninfo; TestSpec *testspec; int i; + PGresult *res; /* * If the user supplies a parameter on the command line, use it as the @@ -61,13 +83,15 @@ main(int argc, char **argv) testspec = &parseresult; printf("Parsed test spec with %d sessions\n", testspec->nsessions); - /* Establish connections to the database, one for each session */ - nconns = testspec->nsessions; + /* + * Establish connections to the database, one for each session and an extra + * for lock wait detection and global work. + */ + nconns = 1 + testspec->nsessions; conns = calloc(nconns, sizeof(PGconn *)); - for (i = 0; i < testspec->nsessions; i++) + backend_ids = calloc(nconns, sizeof(*backend_ids)); + for (i = 0; i < nconns; i++) { - PGresult *res; - conns[i] = PQconnectdb(conninfo); if (PQstatus(conns[i]) != CONNECTION_OK) { @@ -87,6 +111,28 @@ main(int argc, char **argv) exit_nicely(); } PQclear(res); + + /* Get the backend ID for lock wait checking. */ + res = PQexec(conns[i], "SELECT i FROM pg_stat_get_backend_idset() t(i) " + "WHERE pg_stat_get_backend_pid(i) = pg_backend_pid()"); + if (PQresultStatus(res) == PGRES_TUPLES_OK) + { + if (PQntuples(res) == 1 && PQnfields(res) == 1) + backend_ids[i] = strdup(PQgetvalue(res, 0, 0)); + else + { + fprintf(stderr, "backend id query returned %d rows and %d columns, expected 1 row and 1 column", + PQntuples(res), PQnfields(res)); + exit_nicely(); + } + } + else + { + fprintf(stderr, "backend id query failed: %s", + PQerrorMessage(conns[i])); + exit_nicely(); + } + PQclear(res); } /* Set the session index fields in steps. */ @@ -99,6 +145,16 @@ main(int argc, char **argv) session->steps[stepindex]->session = i; } + res = PQprepare(conns[0], PREP_WAITING, + "SELECT 1 WHERE pg_stat_get_backend_waiting($1)", 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + fprintf(stderr, "prepare of lock wait query failed: %s", + PQerrorMessage(conns[0])); + exit_nicely(); + } + PQclear(res); + /* * Run the permutations specified in the spec, or all if none were * explicitly specified. @@ -254,6 +310,7 @@ run_permutation(TestSpec * testspec, int nsteps, Step ** steps) { PGresult *res; int i; + Step *waiting = NULL; printf("\nstarting permutation:"); for (i = 0; i < nsteps; i++) @@ -277,12 +334,12 @@ run_permutation(TestSpec * testspec, int nsteps, Step ** steps) { if (testspec->sessions[i]->setupsql) { - res = PQexec(conns[i], testspec->sessions[i]->setupsql); + res = PQexec(conns[i + 1], 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])); + PQerrorMessage(conns[i + 1])); exit_nicely(); } PQclear(res); @@ -292,44 +349,43 @@ run_permutation(TestSpec * testspec, int nsteps, Step ** steps) /* Perform steps */ for (i = 0; i < nsteps; i++) { - Step *step = steps[i]; + Step *step = steps[i]; - printf("step %s: %s\n", step->name, step->sql); - res = PQexec(conns[step->session], step->sql); - - switch (PQresultStatus(res)) + if (!PQsendQuery(conns[1 + step->session], step->sql)) { - 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))); + fprintf(stdout, "failed to send query: %s\n", + PQerrorMessage(conns[1 + step->session])); + exit_nicely(); } - PQclear(res); + + if (waiting != NULL) + { + /* Some other step is already waiting: just block. */ + try_complete_step(step, 0); + + /* See if this step unblocked the waiting step. */ + if (!try_complete_step(waiting, STEP_NONBLOCK | STEP_RETRY)) + waiting = NULL; + } + else if (try_complete_step(step, STEP_NONBLOCK)) + waiting = step; } + /* Finish any waiting query. */ + if (waiting != NULL) + try_complete_step(waiting, STEP_RETRY); + /* Perform per-session teardown */ for (i = 0; i < testspec->nsessions; i++) { if (testspec->sessions[i]->teardownsql) { - res = PQexec(conns[i], testspec->sessions[i]->teardownsql); + res = PQexec(conns[i + 1], 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])); + PQerrorMessage(conns[i + 1])); /* don't exit on teardown failure */ } PQclear(res); @@ -351,6 +407,105 @@ run_permutation(TestSpec * testspec, int nsteps, Step ** steps) } } +/* + * Our caller already sent the query associated with this step. Wait for it + * to either complete or (if given the STEP_NONBLOCK flag) to block while + * waiting for a lock. We assume that any lock wait will persist until we + * have executed additional steps in the permutation. This is not fully + * robust -- a concurrent autovacuum could briefly take a lock with which we + * conflict. The risk may be low enough to discount. + * + * When calling this function on behalf of a given step for a second or later + * time, pass the STEP_RETRY flag. This only affects the messages printed. + * + * If the STEP_NONBLOCK flag was specified and the query is waiting to acquire + * a lock, returns true. Otherwise, returns false. + */ +static bool +try_complete_step(Step *step, int flags) +{ + PGconn *conn = conns[1 + step->session]; + fd_set read_set; + struct timeval timeout; + int sock = PQsocket(conn); + int ret; + PGresult *res; + + FD_ZERO(&read_set); + + while (flags & STEP_NONBLOCK && PQisBusy(conn)) + { + FD_SET(sock, &read_set); + timeout.tv_sec = 0; + timeout.tv_usec = 10000; /* Check for lock waits every 10ms. */ + + ret = select(sock + 1, &read_set, NULL, NULL, &timeout); + if (ret < 0) /* error in select() */ + { + fprintf(stderr, "select failed: %s\n", strerror(errno)); + exit_nicely(); + } + else if (ret == 0) /* select() timeout: check for lock wait */ + { + int ntuples; + + res = PQexecPrepared(conns[0], PREP_WAITING, 1, + &backend_ids[step->session + 1], + NULL, NULL, 0); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + fprintf(stderr, "lock wait query failed: %s", + PQerrorMessage(conn)); + exit_nicely(); + } + ntuples = PQntuples(res); + PQclear(res); + + if (ntuples >= 1) /* waiting to acquire a lock */ + { + if (!(flags & STEP_RETRY)) + printf("step %s: %s \n", + step->name, step->sql); + return true; + } + /* else, not waiting: give it more time */ + } + else if (!PQconsumeInput(conn)) /* select(): data available */ + { + fprintf(stderr, "PQconsumeInput failed: %s", PQerrorMessage(conn)); + exit_nicely(); + } + } + + if (flags & STEP_RETRY) + printf("step %s: <... completed>\n", step->name); + else + printf("step %s: %s\n", step->name, step->sql); + + while ((res = PQgetResult(conn))) + { + 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); + } + + return false; +} + static void printResultSet(PGresult *res) { diff --git a/src/test/isolation/specs/fk-contention.spec b/src/test/isolation/specs/fk-contention.spec new file mode 100644 index 0000000000..8481ae4e33 --- /dev/null +++ b/src/test/isolation/specs/fk-contention.spec @@ -0,0 +1,19 @@ +setup +{ + CREATE TABLE foo (a int PRIMARY KEY, b text); + CREATE TABLE bar (a int NOT NULL REFERENCES foo); + INSERT INTO foo VALUES (42); +} + +teardown +{ + DROP TABLE foo, bar; +} + +session "s1" +setup { BEGIN; } +step "ins" { INSERT INTO bar VALUES (42); } +step "com" { COMMIT; } + +session "s2" +step "upd" { UPDATE foo SET b = 'Hello World'; } diff --git a/src/test/isolation/specs/fk-deadlock.spec b/src/test/isolation/specs/fk-deadlock.spec new file mode 100644 index 0000000000..530cf10839 --- /dev/null +++ b/src/test/isolation/specs/fk-deadlock.spec @@ -0,0 +1,54 @@ +setup +{ + CREATE TABLE parent ( + parent_key int PRIMARY KEY, + aux text NOT NULL + ); + + CREATE TABLE child ( + child_key int PRIMARY KEY, + parent_key int NOT NULL REFERENCES parent + ); + + INSERT INTO parent VALUES (1, 'foo'); +} + +teardown +{ + DROP TABLE parent, child; +} + +session "s1" +setup { BEGIN; } +step "s1i" { INSERT INTO child VALUES (1, 1); } +step "s1u" { UPDATE parent SET aux = 'bar'; } +step "s1c" { COMMIT; } + +session "s2" +setup { BEGIN; } +step "s2i" { INSERT INTO child VALUES (2, 1); } +step "s2u" { UPDATE parent SET aux = 'baz'; } +step "s2c" { COMMIT; } + +## Most theoretical permutations require that a blocked session execute a +## command, making them impossible in practice. +permutation "s1i" "s1u" "s1c" "s2i" "s2u" "s2c" +permutation "s1i" "s1u" "s2i" "s1c" "s2u" "s2c" +#permutation "s1i" "s1u" "s2i" "s2u" "s1c" "s2c" +#permutation "s1i" "s1u" "s2i" "s2u" "s2c" "s1c" +#permutation "s1i" "s2i" "s1u" "s1c" "s2u" "s2c" +permutation "s1i" "s2i" "s1u" "s2u" "s1c" "s2c" +#permutation "s1i" "s2i" "s1u" "s2u" "s2c" "s1c" +#permutation "s1i" "s2i" "s2u" "s1u" "s1c" "s2c" +permutation "s1i" "s2i" "s2u" "s1u" "s2c" "s1c" +#permutation "s1i" "s2i" "s2u" "s2c" "s1u" "s1c" +#permutation "s2i" "s1i" "s1u" "s1c" "s2u" "s2c" +permutation "s2i" "s1i" "s1u" "s2u" "s1c" "s2c" +#permutation "s2i" "s1i" "s1u" "s2u" "s2c" "s1c" +#permutation "s2i" "s1i" "s2u" "s1u" "s1c" "s2c" +permutation "s2i" "s1i" "s2u" "s1u" "s2c" "s1c" +#permutation "s2i" "s1i" "s2u" "s2c" "s1u" "s1c" +#permutation "s2i" "s2u" "s1i" "s1u" "s1c" "s2c" +#permutation "s2i" "s2u" "s1i" "s1u" "s2c" "s1c" +permutation "s2i" "s2u" "s1i" "s2c" "s1u" "s1c" +#permutation "s2i" "s2u" "s2c" "s1i" "s1u" "s1c" diff --git a/src/test/isolation/specs/fk-deadlock2.spec b/src/test/isolation/specs/fk-deadlock2.spec new file mode 100644 index 0000000000..91a87d13ef --- /dev/null +++ b/src/test/isolation/specs/fk-deadlock2.spec @@ -0,0 +1,59 @@ +setup +{ + CREATE TABLE A ( + AID integer not null, + Col1 integer, + PRIMARY KEY (AID) + ); + + CREATE TABLE B ( + BID integer not null, + AID integer not null, + Col2 integer, + PRIMARY KEY (BID), + FOREIGN KEY (AID) REFERENCES A(AID) + ); + + INSERT INTO A (AID) VALUES (1); + INSERT INTO B (BID,AID) VALUES (2,1); +} + +teardown +{ + DROP TABLE a, b; +} + +session "s1" +setup { BEGIN; } +step "s1u1" { UPDATE A SET Col1 = 1 WHERE AID = 1; } +step "s1u2" { UPDATE B SET Col2 = 1 WHERE BID = 2; } +step "s1c" { COMMIT; } + +session "s2" +setup { BEGIN; } +step "s2u1" { UPDATE B SET Col2 = 1 WHERE BID = 2; } +step "s2u2" { UPDATE B SET Col2 = 1 WHERE BID = 2; } +step "s2c" { COMMIT; } + +## Many theoretical permutations require that a blocked session execute a +## command, making them impossible in practice. +permutation "s1u1" "s1u2" "s1c" "s2u1" "s2u2" "s2c" +permutation "s1u1" "s1u2" "s2u1" "s1c" "s2u2" "s2c" +#permutation "s1u1" "s1u2" "s2u1" "s2u2" "s1c" "s2c" +#permutation "s1u1" "s1u2" "s2u1" "s2u2" "s2c" "s1c" +#permutation "s1u1" "s2u1" "s1u2" "s1c" "s2u2" "s2c" +permutation "s1u1" "s2u1" "s1u2" "s2u2" "s1c" "s2c" +permutation "s1u1" "s2u1" "s1u2" "s2u2" "s2c" "s1c" +permutation "s1u1" "s2u1" "s2u2" "s1u2" "s1c" "s2c" +permutation "s1u1" "s2u1" "s2u2" "s1u2" "s2c" "s1c" +#permutation "s1u1" "s2u1" "s2u2" "s2c" "s1u2" "s1c" +#permutation "s2u1" "s1u1" "s1u2" "s1c" "s2u2" "s2c" +permutation "s2u1" "s1u1" "s1u2" "s2u2" "s1c" "s2c" +permutation "s2u1" "s1u1" "s1u2" "s2u2" "s2c" "s1c" +permutation "s2u1" "s1u1" "s2u2" "s1u2" "s1c" "s2c" +permutation "s2u1" "s1u1" "s2u2" "s1u2" "s2c" "s1c" +#permutation "s2u1" "s1u1" "s2u2" "s2c" "s1u2" "s1c" +#permutation "s2u1" "s2u2" "s1u1" "s1u2" "s1c" "s2c" +#permutation "s2u1" "s2u2" "s1u1" "s1u2" "s2c" "s1c" +permutation "s2u1" "s2u2" "s1u1" "s2c" "s1u2" "s1c" +#permutation "s2u1" "s2u2" "s2c" "s1u1" "s1u2" "s1c"