fklocks-tests-v1.patch

text/plain

Filename: fklocks-tests-v1.patch
Type: text/plain
Part: 0
Message: Re: FOR KEY LOCK foreign keys

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: context
Series: patch v1
File+
src/test/isolation/expected/fk-contention.out 16 0
src/test/isolation/expected/fk-deadlock2.out 106 0
src/test/isolation/expected/fk-deadlock.out 63 0
src/test/isolation/isolation_schedule 3 0
src/test/isolation/isolationtester.c 180 1
src/test/isolation/specs/fk-contention.spec 19 0
src/test/isolation/specs/fk-deadlock2.spec 59 0
src/test/isolation/specs/fk-deadlock.spec 54 0
*** /dev/null
--- b/src/test/isolation/expected/fk-contention.out
***************
*** 0 ****
--- 1,16 ----
+ 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; 
+ 
+ starting permutation: upd ins com
+ step upd:  UPDATE foo SET b = 'Hello World'; 
+ step ins:  INSERT INTO bar VALUES (42); 
+ step com:  COMMIT; 
*** /dev/null
--- b/src/test/isolation/expected/fk-deadlock.out
***************
*** 0 ****
--- 1,63 ----
+ 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);  <waiting ...>
+ 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';  <waiting ...>
+ step s1c:  COMMIT; 
+ step s2u: <... completed>
+ 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';  <waiting ...>
+ step s2c:  COMMIT; 
+ step s1u: <... completed>
+ 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';  <waiting ...>
+ step s1c:  COMMIT; 
+ step s2u: <... completed>
+ 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';  <waiting ...>
+ step s2c:  COMMIT; 
+ step s1u: <... completed>
+ 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);  <waiting ...>
+ step s2c:  COMMIT; 
+ step s1i: <... completed>
+ step s1u:  UPDATE parent SET aux = 'bar'; 
+ step s1c:  COMMIT; 
*** /dev/null
--- b/src/test/isolation/expected/fk-deadlock2.out
***************
*** 0 ****
--- 1,106 ----
+ 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;  <waiting ...>
+ 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;  <waiting ...>
+ 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;  <waiting ...>
+ 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;  <waiting ...>
+ 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;  <waiting ...>
+ 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;  <waiting ...>
+ 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;  <waiting ...>
+ 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;  <waiting ...>
+ 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;  <waiting ...>
+ 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 s1u2:  UPDATE B SET Col2 = 1 WHERE BID = 2; 
+ step s1c:  COMMIT; 
*** a/src/test/isolation/isolation_schedule
--- b/src/test/isolation/isolation_schedule
***************
*** 9,11 **** test: ri-trigger
--- 9,14 ----
  test: partial-index
  test: two-ids
  test: multiple-row-versions
+ test: fk-contention
+ test: fk-deadlock
+ test: fk-deadlock2
*** a/src/test/isolation/isolationtester.c
--- b/src/test/isolation/isolationtester.c
***************
*** 9,23 ****
  #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);
--- 9,36 ----
  #include <windows.h>
  #endif
  
+ #include <errno.h>
+ #include <unistd.h>
  #include <stddef.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  
+ #ifdef HAVE_SYS_SELECT_H
+ #include <sys/select.h>
+ #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);
***************
*** 25,30 **** static void run_all_permutations_recurse(TestSpec *testspec, int nsteps, Step **
--- 38,47 ----
  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 int 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);
  
***************
*** 46,51 **** main(int argc, char **argv)
--- 63,69 ----
  	const char *conninfo;
  	TestSpec   *testspec;
  	int			i;
+ 	PGresult   *res;
  
  	/*
  	 * If the user supplies a parameter on the command line, use it as the
***************
*** 63,75 **** 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;
  	conns = calloc(nconns, sizeof(PGconn *));
! 	for (i = 0; i < testspec->nsessions; i++)
  	{
- 		PGresult *res;
- 
  		conns[i] = PQconnectdb(conninfo);
  		if (PQstatus(conns[i]) != CONNECTION_OK)
  		{
--- 81,95 ----
  	testspec = &parseresult;
  	printf("Parsed test spec with %d sessions\n", 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 *));
! 	backend_ids = calloc(nconns, sizeof(*backend_ids));
! 	for (i = 0; i < nconns; i++)
  	{
  		conns[i] = PQconnectdb(conninfo);
  		if (PQstatus(conns[i]) != CONNECTION_OK)
  		{
***************
*** 89,94 **** main(int argc, char **argv)
--- 109,136 ----
  			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. */
***************
*** 100,105 **** main(int argc, char **argv)
--- 142,157 ----
  			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,259 **** run_permutation(TestSpec *testspec, int nsteps, Step **steps)
--- 306,312 ----
  {
  	PGresult *res;
  	int i;
+ 	Step	   *waiting = NULL;
  
  	printf("\nstarting permutation:");
  	for (i = 0; i < nsteps; i++)
***************
*** 277,288 **** run_permutation(TestSpec *testspec, int nsteps, Step **steps)
  	{
  		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);
--- 330,341 ----
  	{
  		if (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[i+1]));
  				exit_nicely();
  			}
  			PQclear(res);
***************
*** 293,334 **** run_permutation(TestSpec *testspec, int nsteps, Step **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);
--- 346,387 ----
  	for (i = 0; i < nsteps; i++)
  	{
  		Step *step = steps[i];
  
! 		if (!PQsendQuery(conns[1 + step->session], step->sql))
  		{
! 			fprintf(stdout, "failed to send query: %s\n",
! 					PQerrorMessage(conns[1 + step->session]));
! 			exit_nicely();
! 		}
  
! 		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+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[i+1]));
  				/* don't exit on teardown failure */
  			}
  			PQclear(res);
***************
*** 350,355 **** run_permutation(TestSpec *testspec, int nsteps, Step **steps)
--- 403,507 ----
  	}
  }
  
+ /*
+  * Our caller already sent the query associated with this step.  Wait for it to
+  * either complete or (only when 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 0.  Otherwise, returns 1.
+  */
+ static int
+ 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 <waiting ...>\n",
+ 						   step->name, step->sql);
+ 				return 0;
+ 			}
+ 			/* 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 1;
+ }
+ 
  static void
  printResultSet(PGresult *res)
  {
*** /dev/null
--- b/src/test/isolation/specs/fk-contention.spec
***************
*** 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'; }
*** /dev/null
--- b/src/test/isolation/specs/fk-deadlock.spec
***************
*** 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"
*** /dev/null
--- b/src/test/isolation/specs/fk-deadlock2.spec
***************
*** 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"