Re: ALTER TABLE lock strength reduction patch is unsafe

Started by Simon Riggsover 12 years ago115 messages
#1Simon Riggs
simon@2ndQuadrant.com
1 attachment(s)

On 3 January 2012 18:42, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I wrote:

Another point that requires some thought is that switching SnapshotNow
to be MVCC-based will presumably result in a noticeable increase in each
backend's rate of wanting to acquire snapshots.

BTW, I wonder if this couldn't be ameliorated by establishing some
ground rules about how up-to-date a snapshot really needs to be.
Arguably, it should be okay for successive SnapshotNow scans to use the
same snapshot as long as we have not acquired a new lock in between.
If not, reusing an old snap doesn't introduce any race condition that
wasn't there already.

Now that has been implemented using the above design, we can resubmit
the lock level reduction patch, with thanks to Robert.

Submitted patch passes original complaint/benchmark.

Changes
* various forms of ALTER TABLE, notably ADD constraint and VALIDATE
* CREATE TRIGGER

One minor coirrections to earlier thinking with respect to toast
tables. That might be later relaxed.

Full tests including proof of lock level reductions, plus docs.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

reduce_lock_levels.v13.patchapplication/octet-stream; name=reduce_lock_levels.v13.patchDownload
diff --git a/doc/src/sgml/mvcc.sgml b/doc/src/sgml/mvcc.sgml
index 316add7..25fd1e7 100644
--- a/doc/src/sgml/mvcc.sgml
+++ b/doc/src/sgml/mvcc.sgml
@@ -864,7 +864,7 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         <para>
          Acquired by <command>VACUUM</command> (without <option>FULL</option>),
          <command>ANALYZE</>, <command>CREATE INDEX CONCURRENTLY</>, and
-         some forms of <command>ALTER TABLE</command>.
+         <command>ALTER TABLE VALIDATE</command>.
         </para>
        </listitem>
       </varlistentry>
@@ -905,8 +905,8 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         </para>
 
         <para>
-         This lock mode is not automatically acquired by any
-         <productname>PostgreSQL</productname> command.
+         Acquired by <command>ALTER TABLE</> for subcommand types that
+         affect write operations and by <command>CREATE TRIGGER</>.
         </para>
        </listitem>
       </varlistentry>
@@ -951,7 +951,7 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         </para>
 
         <para>
-         Acquired by the <command>ALTER TABLE</>, <command>DROP TABLE</>,
+         Acquired by the <command>ALTER TABLE</> for rewriting, <command>DROP TABLE</>,
          <command>TRUNCATE</command>, <command>REINDEX</command>,
          <command>CLUSTER</command>, and <command>VACUUM FULL</command>
          commands.
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 2609d4a..8ff07f3 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -83,7 +83,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
 
   <para>
    <command>ALTER TABLE</command> changes the definition of an existing table.
-   There are several subforms:
+   There are several subforms described below. Note that the lock level required
+   differs for each subform. An <literal>ACCESS EXCLUSIVE</literal> lock is held
+   unless explicitly noted. When multiple subcommands are listed, the lock
+   held will be the strictest one required from any subcommand.
 
   <variablelist>
    <varlistentry>
@@ -152,6 +155,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       or <command>UPDATE</> commands; they do not cause rows already in the
       table to change.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -180,6 +186,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <productname>PostgreSQL</productname> query planner, refer to
       <xref linkend="planner-stats">.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -212,6 +221,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       of statistics by the <productname>PostgreSQL</productname> query
       planner, refer to <xref linkend="planner-stats">.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -263,6 +275,12 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       the table, until it is validated by using the <literal>VALIDATE
       CONSTRAINT</literal> option.
      </para>
+     <para>
+      This form requires only a <literal>SHARE ROW EXCLUSIVE</literal> lock
+      on the table being altered. If the constraint is a foreign key then
+      a <literal>SHARE UPDATE EXCLUSIVE</literal> lock is also required on
+      the table referenced by the constraint.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -324,6 +342,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       This form alters the attributes of a constraint that was previously
       created. Currently only foreign key constraints may be altered.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -337,11 +358,17 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       Nothing happens if the constraint is already marked valid.
      </para>
      <para>
-      Validation can be a long process on larger tables and currently requires
-      an <literal>ACCESS EXCLUSIVE</literal> lock.  The value of separating
+      Validation can be a long process on larger tables. The value of separating
       validation from initial creation is that you can defer validation to less
       busy times, or can be used to give additional time to correct pre-existing
-      errors while preventing new errors.
+      errors while preventing new errors. Note also that validation on its own
+      does not prevent normal write commands against the table while it runs.
+     </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock
+      on the table being altered. If the constraint is a foreign key then
+      a <literal>ROW SHARE</literal> lock is also required on
+      the table referenced by the constraint.
      </para>
     </listitem>
    </varlistentry>
@@ -382,6 +409,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       mode, and triggers configured as <literal>ENABLE ALWAYS</literal> will
       fire regardless of the current replication mode.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -407,6 +437,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <xref linkend="SQL-CLUSTER">
       operations.  It does not actually re-cluster the table.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -419,6 +452,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       index specification from the table.  This affects
       future cluster operations that don't specify an index.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -466,6 +502,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       FULL</>, <xref linkend="SQL-CLUSTER"> or one of the forms
       of <command>ALTER TABLE</> that forces a table rewrite.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
 
      <note>
       <para>
@@ -488,6 +527,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       defaults.  As with <literal>SET</>, a table rewrite might be
       needed to update the table entirely.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -516,6 +558,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <literal>FOREIGN KEY</literal> constraints are not considered, but
       this might change in the future.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -528,6 +573,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       Queries against the parent table will no longer include records drawn
       from the target table.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -543,6 +591,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       that <command>CREATE TABLE OF</> would permit an equivalent table
       definition.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -552,6 +603,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
      <para>
       This form dissociates a typed table from its type.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 6708725..635381a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2662,8 +2662,7 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
  * The caller must lock the relation, with an appropriate lock level
  * for the subcommands requested. Any subcommand that needs to rewrite
  * tuples in the table forces the whole command to be executed with
- * AccessExclusiveLock (actually, that is currently required always, but
- * we hope to relax it at some point).	We pass the lock level down
+ * AccessExclusiveLock.  We pass the lock level down
  * so that we can apply it recursively to inherited tables. Note that the
  * lock level we want as we recurse might well be higher than required for
  * that specific subcommand. So we pass down the overall lock requirement,
@@ -2730,30 +2729,8 @@ LOCKMODE
 AlterTableGetLockLevel(List *cmds)
 {
 	/*
-	 * Late in 9.1 dev cycle a number of issues were uncovered with access to
-	 * catalog relations, leading to the decision to re-enforce all DDL at
-	 * AccessExclusiveLock level by default.
-	 *
-	 * The issues are that there is a pervasive assumption in the code that
-	 * the catalogs will not be read unless an AccessExclusiveLock is held. If
-	 * that rule is relaxed, we must protect against a number of potential
-	 * effects - infrequent, but proven possible with test cases where
-	 * multiple DDL operations occur in a stream against frequently accessed
-	 * tables.
-	 *
-	 * 1. Catalog tables were read using SnapshotNow, which has a race bug that
-	 * allows a scan to return no valid rows even when one is present in the
-	 * case of a commit of a concurrent update of the catalog table.
-	 * SnapshotNow also ignores transactions in progress, so takes the latest
-	 * committed version without waiting for the latest changes.
-	 *
-	 * 2. Relcache needs to be internally consistent, so unless we lock the
-	 * definition during reads we have no way to guarantee that.
-	 *
-	 * 3. Catcache access isn't coordinated at all so refreshes can occur at
-	 * any time.
+	 * This only works if we read catalog tables using MVCC snapshots.
 	 */
-#ifdef REDUCED_ALTER_TABLE_LOCK_LEVELS
 	ListCell   *lcmd;
 	LOCKMODE	lockmode = ShareUpdateExclusiveLock;
 
@@ -2787,6 +2764,7 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_SetTableSpace:		/* must rewrite heap */
 			case AT_DropNotNull:		/* may change some SQL plans */
 			case AT_SetNotNull:
+			case AT_SetStorage:			/* may add toast tables, see ATRewriteCatalogs() */
 			case AT_GenericOptions:
 			case AT_AlterColumnGenericOptions:
 				cmd_lockmode = AccessExclusiveLock;
@@ -2807,6 +2785,7 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_DisableTrig:
 			case AT_DisableTrigAll:
 			case AT_DisableTrigUser:
+			case AT_AlterConstraint:
 			case AT_AddIndex:	/* from ADD CONSTRAINT */
 			case AT_AddIndexConstraint:
 				cmd_lockmode = ShareRowExclusiveLock;
@@ -2888,8 +2867,6 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_ReplaceRelOptions:
 			case AT_SetOptions:
 			case AT_ResetOptions:
-			case AT_SetStorage:
-			case AT_AlterConstraint:
 			case AT_ValidateConstraint:
 				cmd_lockmode = ShareUpdateExclusiveLock;
 				break;
@@ -2906,9 +2883,6 @@ AlterTableGetLockLevel(List *cmds)
 		if (cmd_lockmode > lockmode)
 			lockmode = cmd_lockmode;
 	}
-#else
-	LOCKMODE	lockmode = AccessExclusiveLock;
-#endif
 
 	return lockmode;
 }
@@ -3230,6 +3204,13 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
 		}
 	}
 
+	/*
+	 * If we think we might need to add/re-add toast tables then
+	 * we currently need to hold an AccessExclusiveLock.
+	 */
+	if (lockmode < AccessExclusiveLock)
+		return;
+
 	/* Check to see if a toast table must be added. */
 	foreach(ltab, *wqueue)
 	{
@@ -5819,7 +5800,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * table; trying to start with a lesser lock will just create a risk of
 	 * deadlock.)
 	 */
-	pkrel = heap_openrv(fkconstraint->pktable, AccessExclusiveLock);
+	pkrel = heap_openrv(fkconstraint->pktable, ShareUpdateExclusiveLock);
 
 	/*
 	 * Validity checks (permission checks wait till we have the column
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index d86e9ad..8fb9915 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -147,7 +147,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	ObjectAddress myself,
 				referenced;
 
-	rel = heap_openrv(stmt->relation, AccessExclusiveLock);
+	rel = heap_openrv(stmt->relation, ShareUpdateExclusiveLock);
 
 	/*
 	 * Triggers must be on tables or views, and there are additional
@@ -482,8 +482,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * can skip this for internally generated triggers, since the name
 	 * modification above should be sufficient.
 	 *
-	 * NOTE that this is cool only because we have AccessExclusiveLock on the
-	 * relation, so the trigger set won't be changing underneath us.
+	 * NOTE that this is cool only because we have a sufficient lock on the
+	 * relation to ensure the trigger set won't be changing underneath us.
 	 */
 	if (!isInternal)
 	{
@@ -1059,7 +1059,7 @@ RemoveTriggerById(Oid trigOid)
 	 */
 	relid = ((Form_pg_trigger) GETSTRUCT(tup))->tgrelid;
 
-	rel = heap_open(relid, AccessExclusiveLock);
+	rel = heap_open(relid, ShareUpdateExclusiveLock);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
 		rel->rd_rel->relkind != RELKIND_VIEW)
@@ -1225,8 +1225,8 @@ renametrig(RenameStmt *stmt)
 	 * on tgrelid/tgname would complain anyway) and to ensure a trigger does
 	 * exist with oldname.
 	 *
-	 * NOTE that this is cool only because we have AccessExclusiveLock on the
-	 * relation, so the trigger set won't be changing underneath us.
+	 * NOTE that this is cool only because we have a sufficient lock on the
+	 * relation to ensure that the trigger set won't be changing underneath us.
 	 */
 	tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
 
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 18daf95..68b779c 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1827,72 +1827,75 @@ and relnamespace != (select oid from pg_namespace where nspname = 'pg_catalog')
 and c.relname != 'my_locks'
 group by c.relname;
 create table alterlock (f1 int primary key, f2 text);
+insert into alterlock values (1, 'foo');
+create table alterlock2 (f3 int primary key, f1 int);
+insert into alterlock2 values (1, 1);
 begin; alter table alterlock alter column f2 set statistics 150;
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
 begin; alter table alterlock cluster on alterlock_pkey;
 select * from my_locks order by 1;
-    relname     |    max_lockmode     
-----------------+---------------------
- alterlock      | AccessExclusiveLock
- alterlock_pkey | AccessExclusiveLock
+    relname     |       max_lockmode       
+----------------+--------------------------
+ alterlock      | ShareUpdateExclusiveLock
+ alterlock_pkey | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set without cluster;
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 commit;
 begin; alter table alterlock set (fillfactor = 100);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock reset (fillfactor);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set (toast.autovacuum_enabled = off);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set (autovacuum_enabled = off);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock alter column f2 set (n_distinct = 1);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
@@ -1906,13 +1909,67 @@ select * from my_locks order by 1;
 rollback;
 begin; alter table alterlock alter column f2 set default 'x';
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |     max_lockmode      
+-----------+-----------------------
+ alterlock | ShareRowExclusiveLock
+(1 row)
+
+rollback;
+begin;
+create trigger ttdummy
+	before delete or update on alterlock
+	for each row
+	execute procedure
+	ttdummy (1, 1);
+select * from my_locks order by 1;
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
+begin;
+select * from my_locks order by 1;
+ relname | max_lockmode 
+---------+--------------
+(0 rows)
+
+alter table alterlock2 add foreign key (f1) references alterlock (f1);
+select * from my_locks order by 1;
+     relname     |       max_lockmode       
+-----------------+--------------------------
+ alterlock       | ShareUpdateExclusiveLock
+ alterlock2      | ShareRowExclusiveLock
+ alterlock2_pkey | AccessShareLock
+ alterlock_pkey  | AccessShareLock
+(4 rows)
+
+rollback;
+begin;
+alter table alterlock2
+add constraint alterlock2nv foreign key (f1) references alterlock (f1) NOT VALID;
+select * from my_locks order by 1;
+  relname   |       max_lockmode       
+------------+--------------------------
+ alterlock  | ShareUpdateExclusiveLock
+ alterlock2 | ShareRowExclusiveLock
+(2 rows)
+
+commit;
+begin;
+alter table alterlock2 validate constraint alterlock2nv;
+select * from my_locks order by 1;
+     relname     |       max_lockmode       
+-----------------+--------------------------
+ alterlock       | RowShareLock
+ alterlock2      | ShareUpdateExclusiveLock
+ alterlock2_pkey | AccessShareLock
+ alterlock_pkey  | AccessShareLock
+(4 rows)
+
+rollback;
 -- cleanup
+drop table alterlock2;
 drop table alterlock;
 drop view my_locks;
 drop type lockmodes;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index dcf8121..0814915 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1275,6 +1275,9 @@ and c.relname != 'my_locks'
 group by c.relname;
 
 create table alterlock (f1 int primary key, f2 text);
+insert into alterlock values (1, 'foo');
+create table alterlock2 (f3 int primary key, f1 int);
+insert into alterlock2 values (1, 1);
 
 begin; alter table alterlock alter column f2 set statistics 150;
 select * from my_locks order by 1;
@@ -1316,7 +1319,33 @@ begin; alter table alterlock alter column f2 set default 'x';
 select * from my_locks order by 1;
 rollback;
 
+begin;
+create trigger ttdummy
+	before delete or update on alterlock
+	for each row
+	execute procedure
+	ttdummy (1, 1);
+select * from my_locks order by 1;
+rollback;
+
+begin;
+select * from my_locks order by 1;
+alter table alterlock2 add foreign key (f1) references alterlock (f1);
+select * from my_locks order by 1;
+rollback;
+
+begin;
+alter table alterlock2
+add constraint alterlock2nv foreign key (f1) references alterlock (f1) NOT VALID;
+select * from my_locks order by 1;
+commit;
+begin;
+alter table alterlock2 validate constraint alterlock2nv;
+select * from my_locks order by 1;
+rollback;
+
 -- cleanup
+drop table alterlock2;
 drop table alterlock;
 drop view my_locks;
 drop type lockmodes;
#2Robert Haas
robertmhaas@gmail.com
In reply to: Simon Riggs (#1)

On Sun, Jul 7, 2013 at 9:24 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

On 3 January 2012 18:42, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I wrote:

Another point that requires some thought is that switching SnapshotNow
to be MVCC-based will presumably result in a noticeable increase in each
backend's rate of wanting to acquire snapshots.

BTW, I wonder if this couldn't be ameliorated by establishing some
ground rules about how up-to-date a snapshot really needs to be.
Arguably, it should be okay for successive SnapshotNow scans to use the
same snapshot as long as we have not acquired a new lock in between.
If not, reusing an old snap doesn't introduce any race condition that
wasn't there already.

Now that has been implemented using the above design, we can resubmit
the lock level reduction patch, with thanks to Robert.

Submitted patch passes original complaint/benchmark.

Changes
* various forms of ALTER TABLE, notably ADD constraint and VALIDATE
* CREATE TRIGGER

One minor coirrections to earlier thinking with respect to toast
tables. That might be later relaxed.

Full tests including proof of lock level reductions, plus docs.

I'm quite glad to see this patch back again for another round. I
haven't reviewed it in detail yet, so the purpose of this email is
just to lay out some general areas of concern and food for thought
rather than to critique anything in the patch too specifically.

Generally, the question on the table is: to what extent do MVCC
catalog scans make the world safe for concurrent DDL, or put
negatively, what hazards remain?

Noah discovered an interesting one recently: apparently, the relcache
machinery has some logic in it that depends on the use of
AccessExclusiveLock in subtle ways. I'm going to attempt to explain
it, and maybe he can jump in and explain it better. Essentially, the
problem is that when a relcache reload occurs, certain data structures
(like the tuple descriptor, but there are others) are compared against
the old version of the same data structure. If there are no changes,
we do nothing; else, we free the old one and install the new one. The
reason why we don't free the old one and install the new one
unconditionally is because other parts of the backend might have
pointers to the old data structure, so just replacing it all the time
would result in crashes. Since DDL requires AccessExclusiveLock +
CheckTableNotInUse(), any actual change to the data structure will
happen when we haven't got any pointers anyway.

A second thing I'm concerned about is that, although MVCC catalog
scans guarantee that we won't miss a concurrently-updated row
entirely, or see a duplicate, they don't guarantee that the rows we
see during a scan of catalog A will be consistent with the rows we see
during a scan of catalog B moments later. For example, hilarity will
ensue if relnatts doesn't match what we see in pg_attribute. Now I
don't think this particular example matters very much because I think
there are probably lots of other things that would also break if we
try to add a column without a full table lock, so we're probably
doomed there anyway. But there might be other instances of this
problem that are more subtle.

I'll try to find some time to look at this in more detail.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#3Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#2)

On 2013-07-15 10:06:31 -0400, Robert Haas wrote:

Noah discovered an interesting one recently: apparently, the relcache
machinery has some logic in it that depends on the use of
AccessExclusiveLock in subtle ways. I'm going to attempt to explain
it, and maybe he can jump in and explain it better. Essentially, the
problem is that when a relcache reload occurs, certain data structures
(like the tuple descriptor, but there are others) are compared against
the old version of the same data structure. If there are no changes,
we do nothing; else, we free the old one and install the new one. The
reason why we don't free the old one and install the new one
unconditionally is because other parts of the backend might have
pointers to the old data structure, so just replacing it all the time
would result in crashes. Since DDL requires AccessExclusiveLock +
CheckTableNotInUse(), any actual change to the data structure will
happen when we haven't got any pointers anyway.

Aren't we swapping in the new data on a data level for that reason? See
RelationClearRelation().

A second thing I'm concerned about is that, although MVCC catalog
scans guarantee that we won't miss a concurrently-updated row
entirely, or see a duplicate, they don't guarantee that the rows we
see during a scan of catalog A will be consistent with the rows we see
during a scan of catalog B moments later. For example, hilarity will
ensue if relnatts doesn't match what we see in pg_attribute. Now I
don't think this particular example matters very much because I think
there are probably lots of other things that would also break if we
try to add a column without a full table lock, so we're probably
doomed there anyway. But there might be other instances of this
problem that are more subtle.

Hm. Other transactions basically should be protected against this
because they can't se uncommitted data anyway, right? ISTM that our own
session basically already has be safe against hilarity of this kind,
right?

I am concerned about stuff like violating constraints because we haven't
yet seen the new constraint definition and the like... Or generating
wrong plans because we haven't seen that somebody has dropped a
constraint and inserted data violating the old one.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#3)

On Mon, Jul 15, 2013 at 10:32 AM, Andres Freund <andres@2ndquadrant.com> wrote:

On 2013-07-15 10:06:31 -0400, Robert Haas wrote:

Noah discovered an interesting one recently: apparently, the relcache
machinery has some logic in it that depends on the use of
AccessExclusiveLock in subtle ways. I'm going to attempt to explain
it, and maybe he can jump in and explain it better. Essentially, the
problem is that when a relcache reload occurs, certain data structures
(like the tuple descriptor, but there are others) are compared against
the old version of the same data structure. If there are no changes,
we do nothing; else, we free the old one and install the new one. The
reason why we don't free the old one and install the new one
unconditionally is because other parts of the backend might have
pointers to the old data structure, so just replacing it all the time
would result in crashes. Since DDL requires AccessExclusiveLock +
CheckTableNotInUse(), any actual change to the data structure will
happen when we haven't got any pointers anyway.

Aren't we swapping in the new data on a data level for that reason? See
RelationClearRelation().

Look at the keep_tupdesc and keep_rules variables.

A second thing I'm concerned about is that, although MVCC catalog
scans guarantee that we won't miss a concurrently-updated row
entirely, or see a duplicate, they don't guarantee that the rows we
see during a scan of catalog A will be consistent with the rows we see
during a scan of catalog B moments later. For example, hilarity will
ensue if relnatts doesn't match what we see in pg_attribute. Now I
don't think this particular example matters very much because I think
there are probably lots of other things that would also break if we
try to add a column without a full table lock, so we're probably
doomed there anyway. But there might be other instances of this
problem that are more subtle.

Hm. Other transactions basically should be protected against this
because they can't se uncommitted data anyway, right? ISTM that our own
session basically already has be safe against hilarity of this kind,
right?

Other transactions are protected only within a single scan. If they
do two or more separate scans one after the another, some other
transaction can commit in the middle of things. Any commits that
happen after starting the first scan and before starting the second
scan will be reflected in the second scan, but not in the first.
That's what I'm concerned about. Our own session can't have a problem
of this kind, because we'll never commit a subtransaction (or, heaven
forbid, a top-level transaction) while in the middle of a system
catalog scan.

I am concerned about stuff like violating constraints because we haven't
yet seen the new constraint definition and the like... Or generating
wrong plans because we haven't seen that somebody has dropped a
constraint and inserted data violating the old one.

Yes, we need to carefully audit the commands for dependencies of that type.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#5Simon Riggs
simon@2ndQuadrant.com
In reply to: Robert Haas (#2)

On 15 July 2013 15:06, Robert Haas <robertmhaas@gmail.com> wrote:

Generally, the question on the table is: to what extent do MVCC
catalog scans make the world safe for concurrent DDL, or put
negatively, what hazards remain?

On Tom's test I've been unable to find a single problem.

Noah discovered an interesting one recently: apparently, the relcache
machinery has some logic in it that depends on the use of
AccessExclusiveLock in subtle ways. I'm going to attempt to explain
it, and maybe he can jump in and explain it better. Essentially, the
problem is that when a relcache reload occurs, certain data structures
(like the tuple descriptor, but there are others) are compared against
the old version of the same data structure. If there are no changes,
we do nothing; else, we free the old one and install the new one. The
reason why we don't free the old one and install the new one
unconditionally is because other parts of the backend might have
pointers to the old data structure, so just replacing it all the time
would result in crashes. Since DDL requires AccessExclusiveLock +
CheckTableNotInUse(), any actual change to the data structure will
happen when we haven't got any pointers anyway.

A second thing I'm concerned about is that, although MVCC catalog
scans guarantee that we won't miss a concurrently-updated row
entirely, or see a duplicate, they don't guarantee that the rows we
see during a scan of catalog A will be consistent with the rows we see
during a scan of catalog B moments later. For example, hilarity will
ensue if relnatts doesn't match what we see in pg_attribute. Now I
don't think this particular example matters very much because I think
there are probably lots of other things that would also break if we
try to add a column without a full table lock, so we're probably
doomed there anyway. But there might be other instances of this
problem that are more subtle.

If you look at this as a generalised problem you probably can find
some issues, but that doesn't mean they apply in the specific cases
we're addressing.

The lock reductions we are discussing in all cases have nothing at all
to do with structure and only relate to various options. Except in the
case of constraints, though even there I see no issues as yet.

I've no problem waiting awhile to apply while others try to find loopholes.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#6Noah Misch
noah@leadboat.com
In reply to: Simon Riggs (#5)

On Mon, Jul 15, 2013 at 05:50:40PM +0100, Simon Riggs wrote:

On 15 July 2013 15:06, Robert Haas <robertmhaas@gmail.com> wrote:

Generally, the question on the table is: to what extent do MVCC
catalog scans make the world safe for concurrent DDL, or put
negatively, what hazards remain?

On Tom's test I've been unable to find a single problem.

Noah discovered an interesting one recently: apparently, the relcache
machinery has some logic in it that depends on the use of
AccessExclusiveLock in subtle ways. I'm going to attempt to explain
it, and maybe he can jump in and explain it better. Essentially, the
problem is that when a relcache reload occurs, certain data structures
(like the tuple descriptor, but there are others) are compared against
the old version of the same data structure. If there are no changes,
we do nothing; else, we free the old one and install the new one. The
reason why we don't free the old one and install the new one
unconditionally is because other parts of the backend might have
pointers to the old data structure, so just replacing it all the time
would result in crashes. Since DDL requires AccessExclusiveLock +
CheckTableNotInUse(), any actual change to the data structure will
happen when we haven't got any pointers anyway.

If you look at this as a generalised problem you probably can find
some issues, but that doesn't mean they apply in the specific cases
we're addressing.

The lock reductions we are discussing in all cases have nothing at all
to do with structure and only relate to various options. Except in the
case of constraints, though even there I see no issues as yet.

I was able to distill the above hypothesis into an actual crash with
reduce_lock_levels.v13.patch. Test recipe:

1. Build with --enable-cassert and with -DCATCACHE_FORCE_RELEASE=1. An
AcceptInvalidationMessages() will then happen at nearly every syscache lookup,
making it far easier to hit a problem of this sort.

2. Run these commands as setup:
create table root (c int);
create table part (check (c > 0), check (c > 0)) inherits (root);

3. Attach a debugger to your session and set a breakpoint at plancat.c:660 (as
of commit 16f38f72ab2b8a3b2d45ba727d213bb31111cea4).

4. Run this in your session; the breakpoint will trip:
select * from root where c = -1;

5. Start another session and run:
alter table part add check (c > 0);

6. Exit the debugger to release the first session. It will crash.

plancache.c:657 stashes a pointer to memory belonging to the rd_att of a
relcache entry. It then proceeds to call eval_const_expressions(), which
performs a syscache lookup in its simplify_function() subroutine. Under
CATCACHE_FORCE_RELEASE, the syscache lookup reliably prompts an
AcceptInvalidationMessages(). The ensuing RelationClearRelation() against
"part" notices the new constraint, decides keep_tupdesc = false, and frees the
existing tupdesc. plancache.c is now left holding a pointer into freed
memory. The next loop iteration will crash when it dereferences a pointer
stored within that freed memory at plancat.c:671.

A remediation strategy that seemed attractive when I last contemplated this
problem is to repoint rd_att immediately but arrange to free the obsolete
TupleDesc in AtEOXact_RelationCache().

--
Noah Misch
EnterpriseDB http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#7Simon Riggs
simon@2ndQuadrant.com
In reply to: Noah Misch (#6)

On 1 August 2013 01:53, Noah Misch <noah@leadboat.com> wrote:

I was able to distill the above hypothesis into an actual crash with
reduce_lock_levels.v13.patch. Test recipe:

Thank you for the report; thank you again for reporting in sufficient
time to allow me to investigate and fix by the next CF.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#8Simon Riggs
simon@2ndQuadrant.com
In reply to: Noah Misch (#6)

On 1 August 2013 01:53, Noah Misch <noah@leadboat.com> wrote:

A remediation strategy that seemed attractive when I last contemplated this
problem is to repoint rd_att immediately but arrange to free the obsolete
TupleDesc in AtEOXact_RelationCache().

I agree that the best way to resolve this is to retain a copy of the
TupleDesc, so that copied pointers to it remain valid.

EOXact is actually longer than strictly necessary in some cases, but
trying to work out a more minimal approach seems hard and possibly
inefficient.

Comments in relcache.c indicate that the Relation swapping concept
might be replaced by refcounting approach. I can't see how that
differs from your suggested route.

Which means I can't see any other way of doing this other than the way
you suggest. Will implement.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#9Simon Riggs
simon@2ndQuadrant.com
In reply to: Simon Riggs (#1)
1 attachment(s)

On 7 July 2013 14:24, Simon Riggs <simon@2ndquadrant.com> wrote:

On 3 January 2012 18:42, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I wrote:

Another point that requires some thought is that switching SnapshotNow
to be MVCC-based will presumably result in a noticeable increase in each
backend's rate of wanting to acquire snapshots.

BTW, I wonder if this couldn't be ameliorated by establishing some
ground rules about how up-to-date a snapshot really needs to be.
Arguably, it should be okay for successive SnapshotNow scans to use the
same snapshot as long as we have not acquired a new lock in between.
If not, reusing an old snap doesn't introduce any race condition that
wasn't there already.

Now that has been implemented using the above design, we can resubmit
the lock level reduction patch, with thanks to Robert.

Submitted patch passes original complaint/benchmark.

Changes
* various forms of ALTER TABLE, notably ADD constraint and VALIDATE
* CREATE TRIGGER

One minor coirrections to earlier thinking with respect to toast
tables. That might be later relaxed.

Full tests including proof of lock level reductions, plus docs.

Rebased to v14

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

reduce_lock_levels.v14.patchapplication/octet-stream; name=reduce_lock_levels.v14.patchDownload
diff --git a/doc/src/sgml/mvcc.sgml b/doc/src/sgml/mvcc.sgml
index 2ca423c..ccff611 100644
--- a/doc/src/sgml/mvcc.sgml
+++ b/doc/src/sgml/mvcc.sgml
@@ -865,7 +865,7 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         <para>
          Acquired by <command>VACUUM</command> (without <option>FULL</option>),
          <command>ANALYZE</>, <command>CREATE INDEX CONCURRENTLY</>, and
-         some forms of <command>ALTER TABLE</command>.
+         <command>ALTER TABLE VALIDATE</command>.
         </para>
        </listitem>
       </varlistentry>
@@ -906,8 +906,8 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         </para>
 
         <para>
-         This lock mode is not automatically acquired by any
-         <productname>PostgreSQL</productname> command.
+         Acquired by <command>ALTER TABLE</> for subcommand types that
+         affect write operations and by <command>CREATE TRIGGER</>.
         </para>
        </listitem>
       </varlistentry>
@@ -951,7 +951,7 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         </para>
 
         <para>
-         Acquired by the <command>ALTER TABLE</>, <command>DROP TABLE</>,
+         Acquired by the <command>ALTER TABLE</> for rewriting, <command>DROP TABLE</>,
          <command>TRUNCATE</command>, <command>REINDEX</command>,
          <command>CLUSTER</command>, and <command>VACUUM FULL</command>
          commands.
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 89649a2..ae8df93 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -84,7 +84,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
 
   <para>
    <command>ALTER TABLE</command> changes the definition of an existing table.
-   There are several subforms:
+   There are several subforms described below. Note that the lock level required
+   differs for each subform. An <literal>ACCESS EXCLUSIVE</literal> lock is held
+   unless explicitly noted. When multiple subcommands are listed, the lock
+   held will be the strictest one required from any subcommand.
 
   <variablelist>
    <varlistentry>
@@ -153,6 +156,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       or <command>UPDATE</> commands; they do not cause rows already in the
       table to change.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -181,6 +187,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <productname>PostgreSQL</productname> query planner, refer to
       <xref linkend="planner-stats">.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -213,6 +222,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       of statistics by the <productname>PostgreSQL</productname> query
       planner, refer to <xref linkend="planner-stats">.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -264,6 +276,12 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       the table, until it is validated by using the <literal>VALIDATE
       CONSTRAINT</literal> option.
      </para>
+     <para>
+      This form requires only a <literal>SHARE ROW EXCLUSIVE</literal> lock
+      on the table being altered. If the constraint is a foreign key then
+      a <literal>SHARE UPDATE EXCLUSIVE</literal> lock is also required on
+      the table referenced by the constraint.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -325,6 +343,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       This form alters the attributes of a constraint that was previously
       created. Currently only foreign key constraints may be altered.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -338,11 +359,17 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       Nothing happens if the constraint is already marked valid.
      </para>
      <para>
-      Validation can be a long process on larger tables and currently requires
-      an <literal>ACCESS EXCLUSIVE</literal> lock.  The value of separating
+      Validation can be a long process on larger tables. The value of separating
       validation from initial creation is that you can defer validation to less
       busy times, or can be used to give additional time to correct pre-existing
-      errors while preventing new errors.
+      errors while preventing new errors. Note also that validation on its own
+      does not prevent normal write commands against the table while it runs.
+     </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock
+      on the table being altered. If the constraint is a foreign key then
+      a <literal>ROW SHARE</literal> lock is also required on
+      the table referenced by the constraint.
      </para>
     </listitem>
    </varlistentry>
@@ -383,6 +410,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       mode, and triggers configured as <literal>ENABLE ALWAYS</literal> will
       fire regardless of the current replication mode.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -408,6 +438,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <xref linkend="SQL-CLUSTER">
       operations.  It does not actually re-cluster the table.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -420,6 +453,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       index specification from the table.  This affects
       future cluster operations that don't specify an index.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -467,6 +503,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       FULL</>, <xref linkend="SQL-CLUSTER"> or one of the forms
       of <command>ALTER TABLE</> that forces a table rewrite.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
 
      <note>
       <para>
@@ -489,6 +528,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       defaults.  As with <literal>SET</>, a table rewrite might be
       needed to update the table entirely.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -517,6 +559,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <literal>FOREIGN KEY</literal> constraints are not considered, but
       this might change in the future.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -529,6 +574,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       Queries against the parent table will no longer include records drawn
       from the target table.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -544,6 +592,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       that <command>CREATE TABLE OF</> would permit an equivalent table
       definition.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -553,6 +604,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
      <para>
       This form dissociates a typed table from its type.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 466d757..a6baaad 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2664,8 +2664,7 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
  * The caller must lock the relation, with an appropriate lock level
  * for the subcommands requested. Any subcommand that needs to rewrite
  * tuples in the table forces the whole command to be executed with
- * AccessExclusiveLock (actually, that is currently required always, but
- * we hope to relax it at some point).	We pass the lock level down
+ * AccessExclusiveLock.  We pass the lock level down
  * so that we can apply it recursively to inherited tables. Note that the
  * lock level we want as we recurse might well be higher than required for
  * that specific subcommand. So we pass down the overall lock requirement,
@@ -2732,30 +2731,8 @@ LOCKMODE
 AlterTableGetLockLevel(List *cmds)
 {
 	/*
-	 * Late in 9.1 dev cycle a number of issues were uncovered with access to
-	 * catalog relations, leading to the decision to re-enforce all DDL at
-	 * AccessExclusiveLock level by default.
-	 *
-	 * The issues are that there is a pervasive assumption in the code that
-	 * the catalogs will not be read unless an AccessExclusiveLock is held. If
-	 * that rule is relaxed, we must protect against a number of potential
-	 * effects - infrequent, but proven possible with test cases where
-	 * multiple DDL operations occur in a stream against frequently accessed
-	 * tables.
-	 *
-	 * 1. Catalog tables were read using SnapshotNow, which has a race bug that
-	 * allows a scan to return no valid rows even when one is present in the
-	 * case of a commit of a concurrent update of the catalog table.
-	 * SnapshotNow also ignores transactions in progress, so takes the latest
-	 * committed version without waiting for the latest changes.
-	 *
-	 * 2. Relcache needs to be internally consistent, so unless we lock the
-	 * definition during reads we have no way to guarantee that.
-	 *
-	 * 3. Catcache access isn't coordinated at all so refreshes can occur at
-	 * any time.
+	 * This only works if we read catalog tables using MVCC snapshots.
 	 */
-#ifdef REDUCED_ALTER_TABLE_LOCK_LEVELS
 	ListCell   *lcmd;
 	LOCKMODE	lockmode = ShareUpdateExclusiveLock;
 
@@ -2789,6 +2766,7 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_SetTableSpace:		/* must rewrite heap */
 			case AT_DropNotNull:		/* may change some SQL plans */
 			case AT_SetNotNull:
+			case AT_SetStorage:			/* may add toast tables, see ATRewriteCatalogs() */
 			case AT_GenericOptions:
 			case AT_AlterColumnGenericOptions:
 				cmd_lockmode = AccessExclusiveLock;
@@ -2809,6 +2787,7 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_DisableTrig:
 			case AT_DisableTrigAll:
 			case AT_DisableTrigUser:
+			case AT_AlterConstraint:
 			case AT_AddIndex:	/* from ADD CONSTRAINT */
 			case AT_AddIndexConstraint:
 			case AT_ReplicaIdentity:
@@ -2891,8 +2870,6 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_ReplaceRelOptions:
 			case AT_SetOptions:
 			case AT_ResetOptions:
-			case AT_SetStorage:
-			case AT_AlterConstraint:
 			case AT_ValidateConstraint:
 				cmd_lockmode = ShareUpdateExclusiveLock;
 				break;
@@ -2909,9 +2886,6 @@ AlterTableGetLockLevel(List *cmds)
 		if (cmd_lockmode > lockmode)
 			lockmode = cmd_lockmode;
 	}
-#else
-	LOCKMODE	lockmode = AccessExclusiveLock;
-#endif
 
 	return lockmode;
 }
@@ -3239,6 +3213,13 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
 		}
 	}
 
+	/*
+	 * If we think we might need to add/re-add toast tables then
+	 * we currently need to hold an AccessExclusiveLock.
+	 */
+	if (lockmode < AccessExclusiveLock)
+		return;
+
 	/* Check to see if a toast table must be added. */
 	foreach(ltab, *wqueue)
 	{
@@ -5845,7 +5826,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * table; trying to start with a lesser lock will just create a risk of
 	 * deadlock.)
 	 */
-	pkrel = heap_openrv(fkconstraint->pktable, AccessExclusiveLock);
+	pkrel = heap_openrv(fkconstraint->pktable, ShareUpdateExclusiveLock);
 
 	/*
 	 * Validity checks (permission checks wait till we have the column
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 86449a6..b3b1f7a 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -147,7 +147,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	ObjectAddress myself,
 				referenced;
 
-	rel = heap_openrv(stmt->relation, AccessExclusiveLock);
+	rel = heap_openrv(stmt->relation, ShareUpdateExclusiveLock);
 
 	/*
 	 * Triggers must be on tables or views, and there are additional
@@ -482,8 +482,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * can skip this for internally generated triggers, since the name
 	 * modification above should be sufficient.
 	 *
-	 * NOTE that this is cool only because we have AccessExclusiveLock on the
-	 * relation, so the trigger set won't be changing underneath us.
+	 * NOTE that this is cool only because we have a sufficient lock on the
+	 * relation to ensure the trigger set won't be changing underneath us.
 	 */
 	if (!isInternal)
 	{
@@ -1059,7 +1059,7 @@ RemoveTriggerById(Oid trigOid)
 	 */
 	relid = ((Form_pg_trigger) GETSTRUCT(tup))->tgrelid;
 
-	rel = heap_open(relid, AccessExclusiveLock);
+	rel = heap_open(relid, ShareUpdateExclusiveLock);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
 		rel->rd_rel->relkind != RELKIND_VIEW)
@@ -1225,8 +1225,8 @@ renametrig(RenameStmt *stmt)
 	 * on tgrelid/tgname would complain anyway) and to ensure a trigger does
 	 * exist with oldname.
 	 *
-	 * NOTE that this is cool only because we have AccessExclusiveLock on the
-	 * relation, so the trigger set won't be changing underneath us.
+	 * NOTE that this is cool only because we have a sufficient lock on the
+	 * relation to ensure that the trigger set won't be changing underneath us.
 	 */
 	tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
 
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 0f0c638..316d789 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1840,72 +1840,75 @@ and relnamespace != (select oid from pg_namespace where nspname = 'pg_catalog')
 and c.relname != 'my_locks'
 group by c.relname;
 create table alterlock (f1 int primary key, f2 text);
+insert into alterlock values (1, 'foo');
+create table alterlock2 (f3 int primary key, f1 int);
+insert into alterlock2 values (1, 1);
 begin; alter table alterlock alter column f2 set statistics 150;
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
 begin; alter table alterlock cluster on alterlock_pkey;
 select * from my_locks order by 1;
-    relname     |    max_lockmode     
-----------------+---------------------
- alterlock      | AccessExclusiveLock
- alterlock_pkey | AccessExclusiveLock
+    relname     |       max_lockmode       
+----------------+--------------------------
+ alterlock      | ShareUpdateExclusiveLock
+ alterlock_pkey | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set without cluster;
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 commit;
 begin; alter table alterlock set (fillfactor = 100);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock reset (fillfactor);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set (toast.autovacuum_enabled = off);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set (autovacuum_enabled = off);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock alter column f2 set (n_distinct = 1);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
@@ -1919,13 +1922,67 @@ select * from my_locks order by 1;
 rollback;
 begin; alter table alterlock alter column f2 set default 'x';
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |     max_lockmode      
+-----------+-----------------------
+ alterlock | ShareRowExclusiveLock
+(1 row)
+
+rollback;
+begin;
+create trigger ttdummy
+	before delete or update on alterlock
+	for each row
+	execute procedure
+	ttdummy (1, 1);
+select * from my_locks order by 1;
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
+begin;
+select * from my_locks order by 1;
+ relname | max_lockmode 
+---------+--------------
+(0 rows)
+
+alter table alterlock2 add foreign key (f1) references alterlock (f1);
+select * from my_locks order by 1;
+     relname     |       max_lockmode       
+-----------------+--------------------------
+ alterlock       | ShareUpdateExclusiveLock
+ alterlock2      | ShareRowExclusiveLock
+ alterlock2_pkey | AccessShareLock
+ alterlock_pkey  | AccessShareLock
+(4 rows)
+
+rollback;
+begin;
+alter table alterlock2
+add constraint alterlock2nv foreign key (f1) references alterlock (f1) NOT VALID;
+select * from my_locks order by 1;
+  relname   |       max_lockmode       
+------------+--------------------------
+ alterlock  | ShareUpdateExclusiveLock
+ alterlock2 | ShareRowExclusiveLock
+(2 rows)
+
+commit;
+begin;
+alter table alterlock2 validate constraint alterlock2nv;
+select * from my_locks order by 1;
+     relname     |       max_lockmode       
+-----------------+--------------------------
+ alterlock       | RowShareLock
+ alterlock2      | ShareUpdateExclusiveLock
+ alterlock2_pkey | AccessShareLock
+ alterlock_pkey  | AccessShareLock
+(4 rows)
+
+rollback;
 -- cleanup
+drop table alterlock2;
 drop table alterlock;
 drop view my_locks;
 drop type lockmodes;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 87973c1..a2ad863 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1283,6 +1283,9 @@ and c.relname != 'my_locks'
 group by c.relname;
 
 create table alterlock (f1 int primary key, f2 text);
+insert into alterlock values (1, 'foo');
+create table alterlock2 (f3 int primary key, f1 int);
+insert into alterlock2 values (1, 1);
 
 begin; alter table alterlock alter column f2 set statistics 150;
 select * from my_locks order by 1;
@@ -1324,7 +1327,33 @@ begin; alter table alterlock alter column f2 set default 'x';
 select * from my_locks order by 1;
 rollback;
 
+begin;
+create trigger ttdummy
+	before delete or update on alterlock
+	for each row
+	execute procedure
+	ttdummy (1, 1);
+select * from my_locks order by 1;
+rollback;
+
+begin;
+select * from my_locks order by 1;
+alter table alterlock2 add foreign key (f1) references alterlock (f1);
+select * from my_locks order by 1;
+rollback;
+
+begin;
+alter table alterlock2
+add constraint alterlock2nv foreign key (f1) references alterlock (f1) NOT VALID;
+select * from my_locks order by 1;
+commit;
+begin;
+alter table alterlock2 validate constraint alterlock2nv;
+select * from my_locks order by 1;
+rollback;
+
 -- cleanup
+drop table alterlock2;
 drop table alterlock;
 drop view my_locks;
 drop type lockmodes;
#10Simon Riggs
simon@2ndQuadrant.com
In reply to: Noah Misch (#6)
1 attachment(s)

On 1 August 2013 01:53, Noah Misch <noah@leadboat.com> wrote:

On Mon, Jul 15, 2013 at 05:50:40PM +0100, Simon Riggs wrote:

On 15 July 2013 15:06, Robert Haas <robertmhaas@gmail.com> wrote:

Generally, the question on the table is: to what extent do MVCC
catalog scans make the world safe for concurrent DDL, or put
negatively, what hazards remain?

On Tom's test I've been unable to find a single problem.

Noah discovered an interesting one recently: apparently, the relcache
machinery has some logic in it that depends on the use of
AccessExclusiveLock in subtle ways. I'm going to attempt to explain
it, and maybe he can jump in and explain it better. Essentially, the
problem is that when a relcache reload occurs, certain data structures
(like the tuple descriptor, but there are others) are compared against
the old version of the same data structure. If there are no changes,
we do nothing; else, we free the old one and install the new one. The
reason why we don't free the old one and install the new one
unconditionally is because other parts of the backend might have
pointers to the old data structure, so just replacing it all the time
would result in crashes. Since DDL requires AccessExclusiveLock +
CheckTableNotInUse(), any actual change to the data structure will
happen when we haven't got any pointers anyway.

If you look at this as a generalised problem you probably can find
some issues, but that doesn't mean they apply in the specific cases
we're addressing.

The lock reductions we are discussing in all cases have nothing at all
to do with structure and only relate to various options. Except in the
case of constraints, though even there I see no issues as yet.

I was able to distill the above hypothesis into an actual crash with
reduce_lock_levels.v13.patch. Test recipe:

1. Build with --enable-cassert and with -DCATCACHE_FORCE_RELEASE=1. An
AcceptInvalidationMessages() will then happen at nearly every syscache lookup,
making it far easier to hit a problem of this sort.

2. Run these commands as setup:
create table root (c int);
create table part (check (c > 0), check (c > 0)) inherits (root);

3. Attach a debugger to your session and set a breakpoint at plancat.c:660 (as
of commit 16f38f72ab2b8a3b2d45ba727d213bb31111cea4).

4. Run this in your session; the breakpoint will trip:
select * from root where c = -1;

5. Start another session and run:
alter table part add check (c > 0);

6. Exit the debugger to release the first session. It will crash.

plancache.c:657 stashes a pointer to memory belonging to the rd_att of a
relcache entry. It then proceeds to call eval_const_expressions(), which
performs a syscache lookup in its simplify_function() subroutine. Under
CATCACHE_FORCE_RELEASE, the syscache lookup reliably prompts an
AcceptInvalidationMessages(). The ensuing RelationClearRelation() against
"part" notices the new constraint, decides keep_tupdesc = false, and frees the
existing tupdesc. plancache.c is now left holding a pointer into freed
memory. The next loop iteration will crash when it dereferences a pointer
stored within that freed memory at plancat.c:671.

A remediation strategy that seemed attractive when I last contemplated this
problem is to repoint rd_att immediately but arrange to free the obsolete
TupleDesc in AtEOXact_RelationCache().

v15 to fix the above problem.

Looking at other potential problems around plancache but nothing found as yet.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

reduce_lock_levels.v15.patchapplication/octet-stream; name=reduce_lock_levels.v15.patchDownload
diff --git a/doc/src/sgml/mvcc.sgml b/doc/src/sgml/mvcc.sgml
index 2ca423c..ccff611 100644
--- a/doc/src/sgml/mvcc.sgml
+++ b/doc/src/sgml/mvcc.sgml
@@ -865,7 +865,7 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         <para>
          Acquired by <command>VACUUM</command> (without <option>FULL</option>),
          <command>ANALYZE</>, <command>CREATE INDEX CONCURRENTLY</>, and
-         some forms of <command>ALTER TABLE</command>.
+         <command>ALTER TABLE VALIDATE</command>.
         </para>
        </listitem>
       </varlistentry>
@@ -906,8 +906,8 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         </para>
 
         <para>
-         This lock mode is not automatically acquired by any
-         <productname>PostgreSQL</productname> command.
+         Acquired by <command>ALTER TABLE</> for subcommand types that
+         affect write operations and by <command>CREATE TRIGGER</>.
         </para>
        </listitem>
       </varlistentry>
@@ -951,7 +951,7 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         </para>
 
         <para>
-         Acquired by the <command>ALTER TABLE</>, <command>DROP TABLE</>,
+         Acquired by the <command>ALTER TABLE</> for rewriting, <command>DROP TABLE</>,
          <command>TRUNCATE</command>, <command>REINDEX</command>,
          <command>CLUSTER</command>, and <command>VACUUM FULL</command>
          commands.
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 89649a2..ae8df93 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -84,7 +84,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
 
   <para>
    <command>ALTER TABLE</command> changes the definition of an existing table.
-   There are several subforms:
+   There are several subforms described below. Note that the lock level required
+   differs for each subform. An <literal>ACCESS EXCLUSIVE</literal> lock is held
+   unless explicitly noted. When multiple subcommands are listed, the lock
+   held will be the strictest one required from any subcommand.
 
   <variablelist>
    <varlistentry>
@@ -153,6 +156,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       or <command>UPDATE</> commands; they do not cause rows already in the
       table to change.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -181,6 +187,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <productname>PostgreSQL</productname> query planner, refer to
       <xref linkend="planner-stats">.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -213,6 +222,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       of statistics by the <productname>PostgreSQL</productname> query
       planner, refer to <xref linkend="planner-stats">.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -264,6 +276,12 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       the table, until it is validated by using the <literal>VALIDATE
       CONSTRAINT</literal> option.
      </para>
+     <para>
+      This form requires only a <literal>SHARE ROW EXCLUSIVE</literal> lock
+      on the table being altered. If the constraint is a foreign key then
+      a <literal>SHARE UPDATE EXCLUSIVE</literal> lock is also required on
+      the table referenced by the constraint.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -325,6 +343,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       This form alters the attributes of a constraint that was previously
       created. Currently only foreign key constraints may be altered.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -338,11 +359,17 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       Nothing happens if the constraint is already marked valid.
      </para>
      <para>
-      Validation can be a long process on larger tables and currently requires
-      an <literal>ACCESS EXCLUSIVE</literal> lock.  The value of separating
+      Validation can be a long process on larger tables. The value of separating
       validation from initial creation is that you can defer validation to less
       busy times, or can be used to give additional time to correct pre-existing
-      errors while preventing new errors.
+      errors while preventing new errors. Note also that validation on its own
+      does not prevent normal write commands against the table while it runs.
+     </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock
+      on the table being altered. If the constraint is a foreign key then
+      a <literal>ROW SHARE</literal> lock is also required on
+      the table referenced by the constraint.
      </para>
     </listitem>
    </varlistentry>
@@ -383,6 +410,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       mode, and triggers configured as <literal>ENABLE ALWAYS</literal> will
       fire regardless of the current replication mode.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -408,6 +438,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <xref linkend="SQL-CLUSTER">
       operations.  It does not actually re-cluster the table.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -420,6 +453,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       index specification from the table.  This affects
       future cluster operations that don't specify an index.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -467,6 +503,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       FULL</>, <xref linkend="SQL-CLUSTER"> or one of the forms
       of <command>ALTER TABLE</> that forces a table rewrite.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
 
      <note>
       <para>
@@ -489,6 +528,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       defaults.  As with <literal>SET</>, a table rewrite might be
       needed to update the table entirely.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -517,6 +559,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <literal>FOREIGN KEY</literal> constraints are not considered, but
       this might change in the future.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -529,6 +574,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       Queries against the parent table will no longer include records drawn
       from the target table.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -544,6 +592,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       that <command>CREATE TABLE OF</> would permit an equivalent table
       definition.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -553,6 +604,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
      <para>
       This form dissociates a typed table from its type.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 466d757..a6baaad 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2664,8 +2664,7 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
  * The caller must lock the relation, with an appropriate lock level
  * for the subcommands requested. Any subcommand that needs to rewrite
  * tuples in the table forces the whole command to be executed with
- * AccessExclusiveLock (actually, that is currently required always, but
- * we hope to relax it at some point).	We pass the lock level down
+ * AccessExclusiveLock.  We pass the lock level down
  * so that we can apply it recursively to inherited tables. Note that the
  * lock level we want as we recurse might well be higher than required for
  * that specific subcommand. So we pass down the overall lock requirement,
@@ -2732,30 +2731,8 @@ LOCKMODE
 AlterTableGetLockLevel(List *cmds)
 {
 	/*
-	 * Late in 9.1 dev cycle a number of issues were uncovered with access to
-	 * catalog relations, leading to the decision to re-enforce all DDL at
-	 * AccessExclusiveLock level by default.
-	 *
-	 * The issues are that there is a pervasive assumption in the code that
-	 * the catalogs will not be read unless an AccessExclusiveLock is held. If
-	 * that rule is relaxed, we must protect against a number of potential
-	 * effects - infrequent, but proven possible with test cases where
-	 * multiple DDL operations occur in a stream against frequently accessed
-	 * tables.
-	 *
-	 * 1. Catalog tables were read using SnapshotNow, which has a race bug that
-	 * allows a scan to return no valid rows even when one is present in the
-	 * case of a commit of a concurrent update of the catalog table.
-	 * SnapshotNow also ignores transactions in progress, so takes the latest
-	 * committed version without waiting for the latest changes.
-	 *
-	 * 2. Relcache needs to be internally consistent, so unless we lock the
-	 * definition during reads we have no way to guarantee that.
-	 *
-	 * 3. Catcache access isn't coordinated at all so refreshes can occur at
-	 * any time.
+	 * This only works if we read catalog tables using MVCC snapshots.
 	 */
-#ifdef REDUCED_ALTER_TABLE_LOCK_LEVELS
 	ListCell   *lcmd;
 	LOCKMODE	lockmode = ShareUpdateExclusiveLock;
 
@@ -2789,6 +2766,7 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_SetTableSpace:		/* must rewrite heap */
 			case AT_DropNotNull:		/* may change some SQL plans */
 			case AT_SetNotNull:
+			case AT_SetStorage:			/* may add toast tables, see ATRewriteCatalogs() */
 			case AT_GenericOptions:
 			case AT_AlterColumnGenericOptions:
 				cmd_lockmode = AccessExclusiveLock;
@@ -2809,6 +2787,7 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_DisableTrig:
 			case AT_DisableTrigAll:
 			case AT_DisableTrigUser:
+			case AT_AlterConstraint:
 			case AT_AddIndex:	/* from ADD CONSTRAINT */
 			case AT_AddIndexConstraint:
 			case AT_ReplicaIdentity:
@@ -2891,8 +2870,6 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_ReplaceRelOptions:
 			case AT_SetOptions:
 			case AT_ResetOptions:
-			case AT_SetStorage:
-			case AT_AlterConstraint:
 			case AT_ValidateConstraint:
 				cmd_lockmode = ShareUpdateExclusiveLock;
 				break;
@@ -2909,9 +2886,6 @@ AlterTableGetLockLevel(List *cmds)
 		if (cmd_lockmode > lockmode)
 			lockmode = cmd_lockmode;
 	}
-#else
-	LOCKMODE	lockmode = AccessExclusiveLock;
-#endif
 
 	return lockmode;
 }
@@ -3239,6 +3213,13 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
 		}
 	}
 
+	/*
+	 * If we think we might need to add/re-add toast tables then
+	 * we currently need to hold an AccessExclusiveLock.
+	 */
+	if (lockmode < AccessExclusiveLock)
+		return;
+
 	/* Check to see if a toast table must be added. */
 	foreach(ltab, *wqueue)
 	{
@@ -5845,7 +5826,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * table; trying to start with a lesser lock will just create a risk of
 	 * deadlock.)
 	 */
-	pkrel = heap_openrv(fkconstraint->pktable, AccessExclusiveLock);
+	pkrel = heap_openrv(fkconstraint->pktable, ShareUpdateExclusiveLock);
 
 	/*
 	 * Validity checks (permission checks wait till we have the column
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 86449a6..b3b1f7a 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -147,7 +147,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	ObjectAddress myself,
 				referenced;
 
-	rel = heap_openrv(stmt->relation, AccessExclusiveLock);
+	rel = heap_openrv(stmt->relation, ShareUpdateExclusiveLock);
 
 	/*
 	 * Triggers must be on tables or views, and there are additional
@@ -482,8 +482,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * can skip this for internally generated triggers, since the name
 	 * modification above should be sufficient.
 	 *
-	 * NOTE that this is cool only because we have AccessExclusiveLock on the
-	 * relation, so the trigger set won't be changing underneath us.
+	 * NOTE that this is cool only because we have a sufficient lock on the
+	 * relation to ensure the trigger set won't be changing underneath us.
 	 */
 	if (!isInternal)
 	{
@@ -1059,7 +1059,7 @@ RemoveTriggerById(Oid trigOid)
 	 */
 	relid = ((Form_pg_trigger) GETSTRUCT(tup))->tgrelid;
 
-	rel = heap_open(relid, AccessExclusiveLock);
+	rel = heap_open(relid, ShareUpdateExclusiveLock);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
 		rel->rd_rel->relkind != RELKIND_VIEW)
@@ -1225,8 +1225,8 @@ renametrig(RenameStmt *stmt)
 	 * on tgrelid/tgname would complain anyway) and to ensure a trigger does
 	 * exist with oldname.
 	 *
-	 * NOTE that this is cool only because we have AccessExclusiveLock on the
-	 * relation, so the trigger set won't be changing underneath us.
+	 * NOTE that this is cool only because we have a sufficient lock on the
+	 * relation to ensure that the trigger set won't be changing underneath us.
 	 */
 	tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
 
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2a46cfc..df39c43 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -161,6 +161,14 @@ static bool eoxact_list_overflowed = false;
 			eoxact_list_overflowed = true; \
 	} while (0)
 
+/*
+ * EOXactTupleDescArray stores *TupleDescs that (might) need AtEOXact
+ * cleanup work.  The array expands as needed; there is no hashtable because
+ * we don't need to access individual items except at EOXact.
+ */
+static TupleDesc *EOXactTupleDescArray;
+static int NextEOXactTupleDescNum = 0;
+static int EOXactTupleDescArrayLen = 0;
 
 /*
  *		macros to manipulate the lookup hashtables
@@ -219,11 +227,12 @@ static HTAB *OpClassCache = NULL;
 
 /* non-export function prototypes */
 
-static void RelationDestroyRelation(Relation relation);
+static void RelationDestroyRelation(Relation relation, bool remember_tupdesc);
 static void RelationClearRelation(Relation relation, bool rebuild);
 
 static void RelationReloadIndexInfo(Relation relation);
 static void RelationFlushRelation(Relation relation);
+static void RememberToFreeTupleDescAtEOX(TupleDesc td);
 static void AtEOXact_cleanup(Relation relation, bool isCommit);
 static void AtEOSubXact_cleanup(Relation relation, bool isCommit,
 					SubTransactionId mySubid, SubTransactionId parentSubid);
@@ -1806,7 +1815,7 @@ RelationReloadIndexInfo(Relation relation)
  *	Caller must already have unhooked the entry from the hash table.
  */
 static void
-RelationDestroyRelation(Relation relation)
+RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 {
 	Assert(RelationHasReferenceCountZero(relation));
 
@@ -1826,7 +1835,12 @@ RelationDestroyRelation(Relation relation)
 	/* can't use DecrTupleDescRefCount here */
 	Assert(relation->rd_att->tdrefcount > 0);
 	if (--relation->rd_att->tdrefcount == 0)
-		FreeTupleDesc(relation->rd_att);
+	{
+		if (remember_tupdesc)
+			RememberToFreeTupleDescAtEOX(relation->rd_att);
+		else
+			FreeTupleDesc(relation->rd_att);
+	}
 	list_free(relation->rd_indexlist);
 	bms_free(relation->rd_indexattr);
 	FreeTriggerDesc(relation->trigdesc);
@@ -1940,7 +1954,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 		RelationCacheDelete(relation);
 
 		/* And release storage */
-		RelationDestroyRelation(relation);
+		RelationDestroyRelation(relation, false);
 	}
 	else
 	{
@@ -1984,7 +1998,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 		{
 			/* Should only get here if relation was deleted */
 			RelationCacheDelete(relation);
-			RelationDestroyRelation(relation);
+			RelationDestroyRelation(relation, false);
 			elog(ERROR, "relation %u deleted while still in use", save_relid);
 		}
 
@@ -2046,7 +2060,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 #undef SWAPFIELD
 
 		/* And now we can throw away the temporary entry */
-		RelationDestroyRelation(newrel);
+		RelationDestroyRelation(newrel, !keep_tupdesc);
 	}
 }
 
@@ -2283,6 +2297,36 @@ RelationCloseSmgrByOid(Oid relationId)
 	RelationCloseSmgr(relation);
 }
 
+void
+RememberToFreeTupleDescAtEOX(TupleDesc td)
+{
+	if (EOXactTupleDescArray == NULL)
+	{
+		MemoryContext	oldcxt;
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+		EOXactTupleDescArray = (TupleDesc *) palloc(16 * sizeof(TupleDesc));
+		EOXactTupleDescArrayLen = 16;
+		NextEOXactTupleDescNum = 0;
+		MemoryContextSwitchTo(oldcxt);
+	}
+	else if (NextEOXactTupleDescNum >= EOXactTupleDescArrayLen)
+	{
+		MemoryContext	oldcxt;
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+		int32       newlen = EOXactTupleDescArrayLen * 2;
+
+		Assert(EOXactTupleDescArrayLen > 0);
+
+		EOXactTupleDescArray = (TupleDesc *) repalloc(EOXactTupleDescArray,
+														newlen * sizeof(TupleDesc));
+		EOXactTupleDescArrayLen = newlen;
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	EOXactTupleDescArray[NextEOXactTupleDescNum++] = td;
+}
+
 /*
  * AtEOXact_RelationCache
  *
@@ -2338,9 +2382,20 @@ AtEOXact_RelationCache(bool isCommit)
 		}
 	}
 
-	/* Now we're out of the transaction and can clear the list */
+	if (EOXactTupleDescArrayLen > 0)
+	{
+		Assert(EOXactTupleDescArray != NULL);
+		for (i = 0; i < NextEOXactTupleDescNum; i++)
+			FreeTupleDesc(EOXactTupleDescArray[i]);
+		pfree(EOXactTupleDescArray);
+		EOXactTupleDescArray = NULL;
+	}
+
+	/* Now we're out of the transaction and can clear the lists */
 	eoxact_list_len = 0;
 	eoxact_list_overflowed = false;
+	NextEOXactTupleDescNum = 0;
+	EOXactTupleDescArrayLen = 0;
 }
 
 /*
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 0f0c638..316d789 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1840,72 +1840,75 @@ and relnamespace != (select oid from pg_namespace where nspname = 'pg_catalog')
 and c.relname != 'my_locks'
 group by c.relname;
 create table alterlock (f1 int primary key, f2 text);
+insert into alterlock values (1, 'foo');
+create table alterlock2 (f3 int primary key, f1 int);
+insert into alterlock2 values (1, 1);
 begin; alter table alterlock alter column f2 set statistics 150;
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
 begin; alter table alterlock cluster on alterlock_pkey;
 select * from my_locks order by 1;
-    relname     |    max_lockmode     
-----------------+---------------------
- alterlock      | AccessExclusiveLock
- alterlock_pkey | AccessExclusiveLock
+    relname     |       max_lockmode       
+----------------+--------------------------
+ alterlock      | ShareUpdateExclusiveLock
+ alterlock_pkey | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set without cluster;
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 commit;
 begin; alter table alterlock set (fillfactor = 100);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock reset (fillfactor);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set (toast.autovacuum_enabled = off);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set (autovacuum_enabled = off);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock alter column f2 set (n_distinct = 1);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
@@ -1919,13 +1922,67 @@ select * from my_locks order by 1;
 rollback;
 begin; alter table alterlock alter column f2 set default 'x';
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |     max_lockmode      
+-----------+-----------------------
+ alterlock | ShareRowExclusiveLock
+(1 row)
+
+rollback;
+begin;
+create trigger ttdummy
+	before delete or update on alterlock
+	for each row
+	execute procedure
+	ttdummy (1, 1);
+select * from my_locks order by 1;
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
+begin;
+select * from my_locks order by 1;
+ relname | max_lockmode 
+---------+--------------
+(0 rows)
+
+alter table alterlock2 add foreign key (f1) references alterlock (f1);
+select * from my_locks order by 1;
+     relname     |       max_lockmode       
+-----------------+--------------------------
+ alterlock       | ShareUpdateExclusiveLock
+ alterlock2      | ShareRowExclusiveLock
+ alterlock2_pkey | AccessShareLock
+ alterlock_pkey  | AccessShareLock
+(4 rows)
+
+rollback;
+begin;
+alter table alterlock2
+add constraint alterlock2nv foreign key (f1) references alterlock (f1) NOT VALID;
+select * from my_locks order by 1;
+  relname   |       max_lockmode       
+------------+--------------------------
+ alterlock  | ShareUpdateExclusiveLock
+ alterlock2 | ShareRowExclusiveLock
+(2 rows)
+
+commit;
+begin;
+alter table alterlock2 validate constraint alterlock2nv;
+select * from my_locks order by 1;
+     relname     |       max_lockmode       
+-----------------+--------------------------
+ alterlock       | RowShareLock
+ alterlock2      | ShareUpdateExclusiveLock
+ alterlock2_pkey | AccessShareLock
+ alterlock_pkey  | AccessShareLock
+(4 rows)
+
+rollback;
 -- cleanup
+drop table alterlock2;
 drop table alterlock;
 drop view my_locks;
 drop type lockmodes;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 87973c1..a2ad863 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1283,6 +1283,9 @@ and c.relname != 'my_locks'
 group by c.relname;
 
 create table alterlock (f1 int primary key, f2 text);
+insert into alterlock values (1, 'foo');
+create table alterlock2 (f3 int primary key, f1 int);
+insert into alterlock2 values (1, 1);
 
 begin; alter table alterlock alter column f2 set statistics 150;
 select * from my_locks order by 1;
@@ -1324,7 +1327,33 @@ begin; alter table alterlock alter column f2 set default 'x';
 select * from my_locks order by 1;
 rollback;
 
+begin;
+create trigger ttdummy
+	before delete or update on alterlock
+	for each row
+	execute procedure
+	ttdummy (1, 1);
+select * from my_locks order by 1;
+rollback;
+
+begin;
+select * from my_locks order by 1;
+alter table alterlock2 add foreign key (f1) references alterlock (f1);
+select * from my_locks order by 1;
+rollback;
+
+begin;
+alter table alterlock2
+add constraint alterlock2nv foreign key (f1) references alterlock (f1) NOT VALID;
+select * from my_locks order by 1;
+commit;
+begin;
+alter table alterlock2 validate constraint alterlock2nv;
+select * from my_locks order by 1;
+rollback;
+
 -- cleanup
+drop table alterlock2;
 drop table alterlock;
 drop view my_locks;
 drop type lockmodes;
#11Peter Geoghegan
pg@heroku.com
In reply to: Simon Riggs (#10)

On Wed, Jan 15, 2014 at 6:57 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

v15 to fix the above problem.

Minor quibble: I get a compiler warning with the patch applied.
"relcache.c: In function ‘RememberToFreeTupleDescAtEOX’:
relcache.c:2317:3: warning: ISO C90 forbids mixed declarations and
code [-Werror=declaration-after-statement]".

--
Peter Geoghegan

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#12Simon Riggs
simon@2ndQuadrant.com
In reply to: Peter Geoghegan (#11)
1 attachment(s)

On 24 January 2014 07:08, Peter Geoghegan <pg@heroku.com> wrote:

On Wed, Jan 15, 2014 at 6:57 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

v15 to fix the above problem.

Minor quibble: I get a compiler warning with the patch applied.
"relcache.c: In function 'RememberToFreeTupleDescAtEOX':
relcache.c:2317:3: warning: ISO C90 forbids mixed declarations and
code [-Werror=declaration-after-statement]".

Thanks, that was a wart, now fixed.

v16 attached

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

reduce_lock_levels.v16.patchapplication/octet-stream; name=reduce_lock_levels.v16.patchDownload
diff --git a/doc/src/sgml/mvcc.sgml b/doc/src/sgml/mvcc.sgml
index 2ca423c..ccff611 100644
--- a/doc/src/sgml/mvcc.sgml
+++ b/doc/src/sgml/mvcc.sgml
@@ -865,7 +865,7 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         <para>
          Acquired by <command>VACUUM</command> (without <option>FULL</option>),
          <command>ANALYZE</>, <command>CREATE INDEX CONCURRENTLY</>, and
-         some forms of <command>ALTER TABLE</command>.
+         <command>ALTER TABLE VALIDATE</command>.
         </para>
        </listitem>
       </varlistentry>
@@ -906,8 +906,8 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         </para>
 
         <para>
-         This lock mode is not automatically acquired by any
-         <productname>PostgreSQL</productname> command.
+         Acquired by <command>ALTER TABLE</> for subcommand types that
+         affect write operations and by <command>CREATE TRIGGER</>.
         </para>
        </listitem>
       </varlistentry>
@@ -951,7 +951,7 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         </para>
 
         <para>
-         Acquired by the <command>ALTER TABLE</>, <command>DROP TABLE</>,
+         Acquired by the <command>ALTER TABLE</> for rewriting, <command>DROP TABLE</>,
          <command>TRUNCATE</command>, <command>REINDEX</command>,
          <command>CLUSTER</command>, and <command>VACUUM FULL</command>
          commands.
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 89649a2..ae8df93 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -84,7 +84,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
 
   <para>
    <command>ALTER TABLE</command> changes the definition of an existing table.
-   There are several subforms:
+   There are several subforms described below. Note that the lock level required
+   differs for each subform. An <literal>ACCESS EXCLUSIVE</literal> lock is held
+   unless explicitly noted. When multiple subcommands are listed, the lock
+   held will be the strictest one required from any subcommand.
 
   <variablelist>
    <varlistentry>
@@ -153,6 +156,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       or <command>UPDATE</> commands; they do not cause rows already in the
       table to change.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -181,6 +187,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <productname>PostgreSQL</productname> query planner, refer to
       <xref linkend="planner-stats">.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -213,6 +222,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       of statistics by the <productname>PostgreSQL</productname> query
       planner, refer to <xref linkend="planner-stats">.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -264,6 +276,12 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       the table, until it is validated by using the <literal>VALIDATE
       CONSTRAINT</literal> option.
      </para>
+     <para>
+      This form requires only a <literal>SHARE ROW EXCLUSIVE</literal> lock
+      on the table being altered. If the constraint is a foreign key then
+      a <literal>SHARE UPDATE EXCLUSIVE</literal> lock is also required on
+      the table referenced by the constraint.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -325,6 +343,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       This form alters the attributes of a constraint that was previously
       created. Currently only foreign key constraints may be altered.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -338,11 +359,17 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       Nothing happens if the constraint is already marked valid.
      </para>
      <para>
-      Validation can be a long process on larger tables and currently requires
-      an <literal>ACCESS EXCLUSIVE</literal> lock.  The value of separating
+      Validation can be a long process on larger tables. The value of separating
       validation from initial creation is that you can defer validation to less
       busy times, or can be used to give additional time to correct pre-existing
-      errors while preventing new errors.
+      errors while preventing new errors. Note also that validation on its own
+      does not prevent normal write commands against the table while it runs.
+     </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock
+      on the table being altered. If the constraint is a foreign key then
+      a <literal>ROW SHARE</literal> lock is also required on
+      the table referenced by the constraint.
      </para>
     </listitem>
    </varlistentry>
@@ -383,6 +410,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       mode, and triggers configured as <literal>ENABLE ALWAYS</literal> will
       fire regardless of the current replication mode.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -408,6 +438,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <xref linkend="SQL-CLUSTER">
       operations.  It does not actually re-cluster the table.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -420,6 +453,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       index specification from the table.  This affects
       future cluster operations that don't specify an index.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -467,6 +503,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       FULL</>, <xref linkend="SQL-CLUSTER"> or one of the forms
       of <command>ALTER TABLE</> that forces a table rewrite.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
 
      <note>
       <para>
@@ -489,6 +528,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       defaults.  As with <literal>SET</>, a table rewrite might be
       needed to update the table entirely.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -517,6 +559,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <literal>FOREIGN KEY</literal> constraints are not considered, but
       this might change in the future.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -529,6 +574,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       Queries against the parent table will no longer include records drawn
       from the target table.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -544,6 +592,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       that <command>CREATE TABLE OF</> would permit an equivalent table
       definition.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -553,6 +604,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
      <para>
       This form dissociates a typed table from its type.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 08b037e..4b80343 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2682,8 +2682,7 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
  * The caller must lock the relation, with an appropriate lock level
  * for the subcommands requested. Any subcommand that needs to rewrite
  * tuples in the table forces the whole command to be executed with
- * AccessExclusiveLock (actually, that is currently required always, but
- * we hope to relax it at some point).	We pass the lock level down
+ * AccessExclusiveLock.  We pass the lock level down
  * so that we can apply it recursively to inherited tables. Note that the
  * lock level we want as we recurse might well be higher than required for
  * that specific subcommand. So we pass down the overall lock requirement,
@@ -2750,30 +2749,8 @@ LOCKMODE
 AlterTableGetLockLevel(List *cmds)
 {
 	/*
-	 * Late in 9.1 dev cycle a number of issues were uncovered with access to
-	 * catalog relations, leading to the decision to re-enforce all DDL at
-	 * AccessExclusiveLock level by default.
-	 *
-	 * The issues are that there is a pervasive assumption in the code that
-	 * the catalogs will not be read unless an AccessExclusiveLock is held. If
-	 * that rule is relaxed, we must protect against a number of potential
-	 * effects - infrequent, but proven possible with test cases where
-	 * multiple DDL operations occur in a stream against frequently accessed
-	 * tables.
-	 *
-	 * 1. Catalog tables were read using SnapshotNow, which has a race bug that
-	 * allows a scan to return no valid rows even when one is present in the
-	 * case of a commit of a concurrent update of the catalog table.
-	 * SnapshotNow also ignores transactions in progress, so takes the latest
-	 * committed version without waiting for the latest changes.
-	 *
-	 * 2. Relcache needs to be internally consistent, so unless we lock the
-	 * definition during reads we have no way to guarantee that.
-	 *
-	 * 3. Catcache access isn't coordinated at all so refreshes can occur at
-	 * any time.
+	 * This only works if we read catalog tables using MVCC snapshots.
 	 */
-#ifdef REDUCED_ALTER_TABLE_LOCK_LEVELS
 	ListCell   *lcmd;
 	LOCKMODE	lockmode = ShareUpdateExclusiveLock;
 
@@ -2807,6 +2784,7 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_SetTableSpace:		/* must rewrite heap */
 			case AT_DropNotNull:		/* may change some SQL plans */
 			case AT_SetNotNull:
+			case AT_SetStorage:			/* may add toast tables, see ATRewriteCatalogs() */
 			case AT_GenericOptions:
 			case AT_AlterColumnGenericOptions:
 				cmd_lockmode = AccessExclusiveLock;
@@ -2827,6 +2805,7 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_DisableTrig:
 			case AT_DisableTrigAll:
 			case AT_DisableTrigUser:
+			case AT_AlterConstraint:
 			case AT_AddIndex:	/* from ADD CONSTRAINT */
 			case AT_AddIndexConstraint:
 			case AT_ReplicaIdentity:
@@ -2909,8 +2888,6 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_ReplaceRelOptions:
 			case AT_SetOptions:
 			case AT_ResetOptions:
-			case AT_SetStorage:
-			case AT_AlterConstraint:
 			case AT_ValidateConstraint:
 				cmd_lockmode = ShareUpdateExclusiveLock;
 				break;
@@ -2927,9 +2904,6 @@ AlterTableGetLockLevel(List *cmds)
 		if (cmd_lockmode > lockmode)
 			lockmode = cmd_lockmode;
 	}
-#else
-	LOCKMODE	lockmode = AccessExclusiveLock;
-#endif
 
 	return lockmode;
 }
@@ -3257,6 +3231,13 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
 		}
 	}
 
+	/*
+	 * If we think we might need to add/re-add toast tables then
+	 * we currently need to hold an AccessExclusiveLock.
+	 */
+	if (lockmode < AccessExclusiveLock)
+		return;
+
 	/* Check to see if a toast table must be added. */
 	foreach(ltab, *wqueue)
 	{
@@ -5863,7 +5844,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * table; trying to start with a lesser lock will just create a risk of
 	 * deadlock.)
 	 */
-	pkrel = heap_openrv(fkconstraint->pktable, AccessExclusiveLock);
+	pkrel = heap_openrv(fkconstraint->pktable, ShareUpdateExclusiveLock);
 
 	/*
 	 * Validity checks (permission checks wait till we have the column
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 86449a6..b3b1f7a 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -147,7 +147,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	ObjectAddress myself,
 				referenced;
 
-	rel = heap_openrv(stmt->relation, AccessExclusiveLock);
+	rel = heap_openrv(stmt->relation, ShareUpdateExclusiveLock);
 
 	/*
 	 * Triggers must be on tables or views, and there are additional
@@ -482,8 +482,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * can skip this for internally generated triggers, since the name
 	 * modification above should be sufficient.
 	 *
-	 * NOTE that this is cool only because we have AccessExclusiveLock on the
-	 * relation, so the trigger set won't be changing underneath us.
+	 * NOTE that this is cool only because we have a sufficient lock on the
+	 * relation to ensure the trigger set won't be changing underneath us.
 	 */
 	if (!isInternal)
 	{
@@ -1059,7 +1059,7 @@ RemoveTriggerById(Oid trigOid)
 	 */
 	relid = ((Form_pg_trigger) GETSTRUCT(tup))->tgrelid;
 
-	rel = heap_open(relid, AccessExclusiveLock);
+	rel = heap_open(relid, ShareUpdateExclusiveLock);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
 		rel->rd_rel->relkind != RELKIND_VIEW)
@@ -1225,8 +1225,8 @@ renametrig(RenameStmt *stmt)
 	 * on tgrelid/tgname would complain anyway) and to ensure a trigger does
 	 * exist with oldname.
 	 *
-	 * NOTE that this is cool only because we have AccessExclusiveLock on the
-	 * relation, so the trigger set won't be changing underneath us.
+	 * NOTE that this is cool only because we have a sufficient lock on the
+	 * relation to ensure that the trigger set won't be changing underneath us.
 	 */
 	tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
 
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2a46cfc..f0baf12 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -161,6 +161,14 @@ static bool eoxact_list_overflowed = false;
 			eoxact_list_overflowed = true; \
 	} while (0)
 
+/*
+ * EOXactTupleDescArray stores *TupleDescs that (might) need AtEOXact
+ * cleanup work.  The array expands as needed; there is no hashtable because
+ * we don't need to access individual items except at EOXact.
+ */
+static TupleDesc *EOXactTupleDescArray;
+static int NextEOXactTupleDescNum = 0;
+static int EOXactTupleDescArrayLen = 0;
 
 /*
  *		macros to manipulate the lookup hashtables
@@ -219,11 +227,12 @@ static HTAB *OpClassCache = NULL;
 
 /* non-export function prototypes */
 
-static void RelationDestroyRelation(Relation relation);
+static void RelationDestroyRelation(Relation relation, bool remember_tupdesc);
 static void RelationClearRelation(Relation relation, bool rebuild);
 
 static void RelationReloadIndexInfo(Relation relation);
 static void RelationFlushRelation(Relation relation);
+static void RememberToFreeTupleDescAtEOX(TupleDesc td);
 static void AtEOXact_cleanup(Relation relation, bool isCommit);
 static void AtEOSubXact_cleanup(Relation relation, bool isCommit,
 					SubTransactionId mySubid, SubTransactionId parentSubid);
@@ -1806,7 +1815,7 @@ RelationReloadIndexInfo(Relation relation)
  *	Caller must already have unhooked the entry from the hash table.
  */
 static void
-RelationDestroyRelation(Relation relation)
+RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 {
 	Assert(RelationHasReferenceCountZero(relation));
 
@@ -1826,7 +1835,12 @@ RelationDestroyRelation(Relation relation)
 	/* can't use DecrTupleDescRefCount here */
 	Assert(relation->rd_att->tdrefcount > 0);
 	if (--relation->rd_att->tdrefcount == 0)
-		FreeTupleDesc(relation->rd_att);
+	{
+		if (remember_tupdesc)
+			RememberToFreeTupleDescAtEOX(relation->rd_att);
+		else
+			FreeTupleDesc(relation->rd_att);
+	}
 	list_free(relation->rd_indexlist);
 	bms_free(relation->rd_indexattr);
 	FreeTriggerDesc(relation->trigdesc);
@@ -1940,7 +1954,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 		RelationCacheDelete(relation);
 
 		/* And release storage */
-		RelationDestroyRelation(relation);
+		RelationDestroyRelation(relation, false);
 	}
 	else
 	{
@@ -1984,7 +1998,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 		{
 			/* Should only get here if relation was deleted */
 			RelationCacheDelete(relation);
-			RelationDestroyRelation(relation);
+			RelationDestroyRelation(relation, false);
 			elog(ERROR, "relation %u deleted while still in use", save_relid);
 		}
 
@@ -2046,7 +2060,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 #undef SWAPFIELD
 
 		/* And now we can throw away the temporary entry */
-		RelationDestroyRelation(newrel);
+		RelationDestroyRelation(newrel, !keep_tupdesc);
 	}
 }
 
@@ -2283,6 +2297,37 @@ RelationCloseSmgrByOid(Oid relationId)
 	RelationCloseSmgr(relation);
 }
 
+void
+RememberToFreeTupleDescAtEOX(TupleDesc td)
+{
+	if (EOXactTupleDescArray == NULL)
+	{
+		MemoryContext	oldcxt;
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+		EOXactTupleDescArray = (TupleDesc *) palloc(16 * sizeof(TupleDesc));
+		EOXactTupleDescArrayLen = 16;
+		NextEOXactTupleDescNum = 0;
+		MemoryContextSwitchTo(oldcxt);
+	}
+	else if (NextEOXactTupleDescNum >= EOXactTupleDescArrayLen)
+	{
+		MemoryContext	oldcxt;
+		int32       	newlen = EOXactTupleDescArrayLen * 2;
+
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+		Assert(EOXactTupleDescArrayLen > 0);
+
+		EOXactTupleDescArray = (TupleDesc *) repalloc(EOXactTupleDescArray,
+														newlen * sizeof(TupleDesc));
+		EOXactTupleDescArrayLen = newlen;
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	EOXactTupleDescArray[NextEOXactTupleDescNum++] = td;
+}
+
 /*
  * AtEOXact_RelationCache
  *
@@ -2338,9 +2383,20 @@ AtEOXact_RelationCache(bool isCommit)
 		}
 	}
 
-	/* Now we're out of the transaction and can clear the list */
+	if (EOXactTupleDescArrayLen > 0)
+	{
+		Assert(EOXactTupleDescArray != NULL);
+		for (i = 0; i < NextEOXactTupleDescNum; i++)
+			FreeTupleDesc(EOXactTupleDescArray[i]);
+		pfree(EOXactTupleDescArray);
+		EOXactTupleDescArray = NULL;
+	}
+
+	/* Now we're out of the transaction and can clear the lists */
 	eoxact_list_len = 0;
 	eoxact_list_overflowed = false;
+	NextEOXactTupleDescNum = 0;
+	EOXactTupleDescArrayLen = 0;
 }
 
 /*
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 0f0c638..316d789 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1840,72 +1840,75 @@ and relnamespace != (select oid from pg_namespace where nspname = 'pg_catalog')
 and c.relname != 'my_locks'
 group by c.relname;
 create table alterlock (f1 int primary key, f2 text);
+insert into alterlock values (1, 'foo');
+create table alterlock2 (f3 int primary key, f1 int);
+insert into alterlock2 values (1, 1);
 begin; alter table alterlock alter column f2 set statistics 150;
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
 begin; alter table alterlock cluster on alterlock_pkey;
 select * from my_locks order by 1;
-    relname     |    max_lockmode     
-----------------+---------------------
- alterlock      | AccessExclusiveLock
- alterlock_pkey | AccessExclusiveLock
+    relname     |       max_lockmode       
+----------------+--------------------------
+ alterlock      | ShareUpdateExclusiveLock
+ alterlock_pkey | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set without cluster;
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 commit;
 begin; alter table alterlock set (fillfactor = 100);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock reset (fillfactor);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set (toast.autovacuum_enabled = off);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set (autovacuum_enabled = off);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock alter column f2 set (n_distinct = 1);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
@@ -1919,13 +1922,67 @@ select * from my_locks order by 1;
 rollback;
 begin; alter table alterlock alter column f2 set default 'x';
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |     max_lockmode      
+-----------+-----------------------
+ alterlock | ShareRowExclusiveLock
+(1 row)
+
+rollback;
+begin;
+create trigger ttdummy
+	before delete or update on alterlock
+	for each row
+	execute procedure
+	ttdummy (1, 1);
+select * from my_locks order by 1;
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
+begin;
+select * from my_locks order by 1;
+ relname | max_lockmode 
+---------+--------------
+(0 rows)
+
+alter table alterlock2 add foreign key (f1) references alterlock (f1);
+select * from my_locks order by 1;
+     relname     |       max_lockmode       
+-----------------+--------------------------
+ alterlock       | ShareUpdateExclusiveLock
+ alterlock2      | ShareRowExclusiveLock
+ alterlock2_pkey | AccessShareLock
+ alterlock_pkey  | AccessShareLock
+(4 rows)
+
+rollback;
+begin;
+alter table alterlock2
+add constraint alterlock2nv foreign key (f1) references alterlock (f1) NOT VALID;
+select * from my_locks order by 1;
+  relname   |       max_lockmode       
+------------+--------------------------
+ alterlock  | ShareUpdateExclusiveLock
+ alterlock2 | ShareRowExclusiveLock
+(2 rows)
+
+commit;
+begin;
+alter table alterlock2 validate constraint alterlock2nv;
+select * from my_locks order by 1;
+     relname     |       max_lockmode       
+-----------------+--------------------------
+ alterlock       | RowShareLock
+ alterlock2      | ShareUpdateExclusiveLock
+ alterlock2_pkey | AccessShareLock
+ alterlock_pkey  | AccessShareLock
+(4 rows)
+
+rollback;
 -- cleanup
+drop table alterlock2;
 drop table alterlock;
 drop view my_locks;
 drop type lockmodes;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 87973c1..a2ad863 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1283,6 +1283,9 @@ and c.relname != 'my_locks'
 group by c.relname;
 
 create table alterlock (f1 int primary key, f2 text);
+insert into alterlock values (1, 'foo');
+create table alterlock2 (f3 int primary key, f1 int);
+insert into alterlock2 values (1, 1);
 
 begin; alter table alterlock alter column f2 set statistics 150;
 select * from my_locks order by 1;
@@ -1324,7 +1327,33 @@ begin; alter table alterlock alter column f2 set default 'x';
 select * from my_locks order by 1;
 rollback;
 
+begin;
+create trigger ttdummy
+	before delete or update on alterlock
+	for each row
+	execute procedure
+	ttdummy (1, 1);
+select * from my_locks order by 1;
+rollback;
+
+begin;
+select * from my_locks order by 1;
+alter table alterlock2 add foreign key (f1) references alterlock (f1);
+select * from my_locks order by 1;
+rollback;
+
+begin;
+alter table alterlock2
+add constraint alterlock2nv foreign key (f1) references alterlock (f1) NOT VALID;
+select * from my_locks order by 1;
+commit;
+begin;
+alter table alterlock2 validate constraint alterlock2nv;
+select * from my_locks order by 1;
+rollback;
+
 -- cleanup
+drop table alterlock2;
 drop table alterlock;
 drop view my_locks;
 drop type lockmodes;
#13Simon Riggs
simon@2ndQuadrant.com
In reply to: Simon Riggs (#12)

On 24 January 2014 08:33, Simon Riggs <simon@2ndquadrant.com> wrote:

On 24 January 2014 07:08, Peter Geoghegan <pg@heroku.com> wrote:

On Wed, Jan 15, 2014 at 6:57 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

v15 to fix the above problem.

v16 attached

v17 attached

This version adds a GUC called ddl_exclusive_locks which allows us to
keep the 9.3 behaviour if we wish it. Some people may be surprised
that their programs don't wait in the same places they used to. We
hope that is a positive and useful behaviour, but it may not always be
so.

I'll commit this on Thurs 30 Jan unless I hear objections.

Thanks

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#14Simon Riggs
simon@2ndQuadrant.com
In reply to: Simon Riggs (#13)
1 attachment(s)

On 27 January 2014 17:58, Simon Riggs <simon@2ndquadrant.com> wrote:

On 24 January 2014 08:33, Simon Riggs <simon@2ndquadrant.com> wrote:

On 24 January 2014 07:08, Peter Geoghegan <pg@heroku.com> wrote:

On Wed, Jan 15, 2014 at 6:57 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

v15 to fix the above problem.

v16 attached

v17 attached

Frostbite...

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

reduce_lock_levels.v17.patchapplication/octet-stream; name=reduce_lock_levels.v17.patchDownload
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 14ed6c7..9fc23f9 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -6045,6 +6045,22 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-ddl-exclusive-locks" xreflabel="ddl_exclusive_locks">
+      <term><varname>ddl_exclusive_locks</varname> (<type>boolean</type>)</term>
+      <indexterm>
+       <primary><varname>ddl_exclusive_locks</> configuration parameter</primary>
+      </indexterm>
+      <listitem>
+       <para>
+        This controls whether <literal>DDL</> commands will be forced
+        to use <literal>AccessExclusiveLock</> while executing.
+        The parameter is <literal>off</> by default; in
+        <productname>PostgreSQL</> 9.3 and earlier, it was on by
+        default.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-escape-string-warning" xreflabel="escape_string_warning">
       <term><varname>escape_string_warning</varname> (<type>boolean</type>)</term>
       <indexterm><primary>strings</><secondary>escape warning</></>
diff --git a/doc/src/sgml/mvcc.sgml b/doc/src/sgml/mvcc.sgml
index 2ca423c..ccff611 100644
--- a/doc/src/sgml/mvcc.sgml
+++ b/doc/src/sgml/mvcc.sgml
@@ -865,7 +865,7 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         <para>
          Acquired by <command>VACUUM</command> (without <option>FULL</option>),
          <command>ANALYZE</>, <command>CREATE INDEX CONCURRENTLY</>, and
-         some forms of <command>ALTER TABLE</command>.
+         <command>ALTER TABLE VALIDATE</command>.
         </para>
        </listitem>
       </varlistentry>
@@ -906,8 +906,8 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         </para>
 
         <para>
-         This lock mode is not automatically acquired by any
-         <productname>PostgreSQL</productname> command.
+         Acquired by <command>ALTER TABLE</> for subcommand types that
+         affect write operations and by <command>CREATE TRIGGER</>.
         </para>
        </listitem>
       </varlistentry>
@@ -951,7 +951,7 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         </para>
 
         <para>
-         Acquired by the <command>ALTER TABLE</>, <command>DROP TABLE</>,
+         Acquired by the <command>ALTER TABLE</> for rewriting, <command>DROP TABLE</>,
          <command>TRUNCATE</command>, <command>REINDEX</command>,
          <command>CLUSTER</command>, and <command>VACUUM FULL</command>
          commands.
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 89649a2..ae8df93 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -84,7 +84,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
 
   <para>
    <command>ALTER TABLE</command> changes the definition of an existing table.
-   There are several subforms:
+   There are several subforms described below. Note that the lock level required
+   differs for each subform. An <literal>ACCESS EXCLUSIVE</literal> lock is held
+   unless explicitly noted. When multiple subcommands are listed, the lock
+   held will be the strictest one required from any subcommand.
 
   <variablelist>
    <varlistentry>
@@ -153,6 +156,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       or <command>UPDATE</> commands; they do not cause rows already in the
       table to change.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -181,6 +187,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <productname>PostgreSQL</productname> query planner, refer to
       <xref linkend="planner-stats">.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -213,6 +222,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       of statistics by the <productname>PostgreSQL</productname> query
       planner, refer to <xref linkend="planner-stats">.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -264,6 +276,12 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       the table, until it is validated by using the <literal>VALIDATE
       CONSTRAINT</literal> option.
      </para>
+     <para>
+      This form requires only a <literal>SHARE ROW EXCLUSIVE</literal> lock
+      on the table being altered. If the constraint is a foreign key then
+      a <literal>SHARE UPDATE EXCLUSIVE</literal> lock is also required on
+      the table referenced by the constraint.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -325,6 +343,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       This form alters the attributes of a constraint that was previously
       created. Currently only foreign key constraints may be altered.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -338,11 +359,17 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       Nothing happens if the constraint is already marked valid.
      </para>
      <para>
-      Validation can be a long process on larger tables and currently requires
-      an <literal>ACCESS EXCLUSIVE</literal> lock.  The value of separating
+      Validation can be a long process on larger tables. The value of separating
       validation from initial creation is that you can defer validation to less
       busy times, or can be used to give additional time to correct pre-existing
-      errors while preventing new errors.
+      errors while preventing new errors. Note also that validation on its own
+      does not prevent normal write commands against the table while it runs.
+     </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock
+      on the table being altered. If the constraint is a foreign key then
+      a <literal>ROW SHARE</literal> lock is also required on
+      the table referenced by the constraint.
      </para>
     </listitem>
    </varlistentry>
@@ -383,6 +410,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       mode, and triggers configured as <literal>ENABLE ALWAYS</literal> will
       fire regardless of the current replication mode.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -408,6 +438,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <xref linkend="SQL-CLUSTER">
       operations.  It does not actually re-cluster the table.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -420,6 +453,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       index specification from the table.  This affects
       future cluster operations that don't specify an index.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -467,6 +503,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       FULL</>, <xref linkend="SQL-CLUSTER"> or one of the forms
       of <command>ALTER TABLE</> that forces a table rewrite.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
 
      <note>
       <para>
@@ -489,6 +528,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       defaults.  As with <literal>SET</>, a table rewrite might be
       needed to update the table entirely.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -517,6 +559,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <literal>FOREIGN KEY</literal> constraints are not considered, but
       this might change in the future.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -529,6 +574,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       Queries against the parent table will no longer include records drawn
       from the target table.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -544,6 +592,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       that <command>CREATE TABLE OF</> would permit an equivalent table
       definition.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -553,6 +604,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
      <para>
       This form dissociates a typed table from its type.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 08b037e..f209ff3 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -89,6 +89,7 @@
 #include "utils/tqual.h"
 #include "utils/typcache.h"
 
+bool	ddl_exclusive_locks = false;
 
 /*
  * ON COMMIT action list
@@ -2682,8 +2683,7 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
  * The caller must lock the relation, with an appropriate lock level
  * for the subcommands requested. Any subcommand that needs to rewrite
  * tuples in the table forces the whole command to be executed with
- * AccessExclusiveLock (actually, that is currently required always, but
- * we hope to relax it at some point).	We pass the lock level down
+ * AccessExclusiveLock.  We pass the lock level down
  * so that we can apply it recursively to inherited tables. Note that the
  * lock level we want as we recurse might well be higher than required for
  * that specific subcommand. So we pass down the overall lock requirement,
@@ -2750,33 +2750,14 @@ LOCKMODE
 AlterTableGetLockLevel(List *cmds)
 {
 	/*
-	 * Late in 9.1 dev cycle a number of issues were uncovered with access to
-	 * catalog relations, leading to the decision to re-enforce all DDL at
-	 * AccessExclusiveLock level by default.
-	 *
-	 * The issues are that there is a pervasive assumption in the code that
-	 * the catalogs will not be read unless an AccessExclusiveLock is held. If
-	 * that rule is relaxed, we must protect against a number of potential
-	 * effects - infrequent, but proven possible with test cases where
-	 * multiple DDL operations occur in a stream against frequently accessed
-	 * tables.
-	 *
-	 * 1. Catalog tables were read using SnapshotNow, which has a race bug that
-	 * allows a scan to return no valid rows even when one is present in the
-	 * case of a commit of a concurrent update of the catalog table.
-	 * SnapshotNow also ignores transactions in progress, so takes the latest
-	 * committed version without waiting for the latest changes.
-	 *
-	 * 2. Relcache needs to be internally consistent, so unless we lock the
-	 * definition during reads we have no way to guarantee that.
-	 *
-	 * 3. Catcache access isn't coordinated at all so refreshes can occur at
-	 * any time.
+	 * This only works if we read catalog tables using MVCC snapshots.
 	 */
-#ifdef REDUCED_ALTER_TABLE_LOCK_LEVELS
 	ListCell   *lcmd;
 	LOCKMODE	lockmode = ShareUpdateExclusiveLock;
 
+	if (ddl_exclusive_locks)
+		return (LOCKMODE) AccessExclusiveLock;
+
 	foreach(lcmd, cmds)
 	{
 		AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
@@ -2807,6 +2788,7 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_SetTableSpace:		/* must rewrite heap */
 			case AT_DropNotNull:		/* may change some SQL plans */
 			case AT_SetNotNull:
+			case AT_SetStorage:			/* may add toast tables, see ATRewriteCatalogs() */
 			case AT_GenericOptions:
 			case AT_AlterColumnGenericOptions:
 				cmd_lockmode = AccessExclusiveLock;
@@ -2827,6 +2809,7 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_DisableTrig:
 			case AT_DisableTrigAll:
 			case AT_DisableTrigUser:
+			case AT_AlterConstraint:
 			case AT_AddIndex:	/* from ADD CONSTRAINT */
 			case AT_AddIndexConstraint:
 			case AT_ReplicaIdentity:
@@ -2909,8 +2892,6 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_ReplaceRelOptions:
 			case AT_SetOptions:
 			case AT_ResetOptions:
-			case AT_SetStorage:
-			case AT_AlterConstraint:
 			case AT_ValidateConstraint:
 				cmd_lockmode = ShareUpdateExclusiveLock;
 				break;
@@ -2927,9 +2908,6 @@ AlterTableGetLockLevel(List *cmds)
 		if (cmd_lockmode > lockmode)
 			lockmode = cmd_lockmode;
 	}
-#else
-	LOCKMODE	lockmode = AccessExclusiveLock;
-#endif
 
 	return lockmode;
 }
@@ -3257,6 +3235,13 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
 		}
 	}
 
+	/*
+	 * If we think we might need to add/re-add toast tables then
+	 * we currently need to hold an AccessExclusiveLock.
+	 */
+	if (lockmode < AccessExclusiveLock)
+		return;
+
 	/* Check to see if a toast table must be added. */
 	foreach(ltab, *wqueue)
 	{
@@ -5863,7 +5848,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * table; trying to start with a lesser lock will just create a risk of
 	 * deadlock.)
 	 */
-	pkrel = heap_openrv(fkconstraint->pktable, AccessExclusiveLock);
+	pkrel = heap_openrv(fkconstraint->pktable, ShareUpdateExclusiveLock);
 
 	/*
 	 * Validity checks (permission checks wait till we have the column
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 86449a6..8fe84a9 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -146,8 +146,12 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	Oid			constrrelid = InvalidOid;
 	ObjectAddress myself,
 				referenced;
+	LOCKMODE	lockmode = ShareUpdateExclusiveLock;
 
-	rel = heap_openrv(stmt->relation, AccessExclusiveLock);
+	if (ddl_exclusive_locks)
+		lockmode = AccessExclusiveLock;
+
+	rel = heap_openrv(stmt->relation, lockmode);
 
 	/*
 	 * Triggers must be on tables or views, and there are additional
@@ -482,8 +486,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * can skip this for internally generated triggers, since the name
 	 * modification above should be sufficient.
 	 *
-	 * NOTE that this is cool only because we have AccessExclusiveLock on the
-	 * relation, so the trigger set won't be changing underneath us.
+	 * NOTE that this is cool only because we have a sufficient lock on the
+	 * relation to ensure the trigger set won't be changing underneath us.
 	 */
 	if (!isInternal)
 	{
@@ -1036,6 +1040,10 @@ RemoveTriggerById(Oid trigOid)
 	HeapTuple	tup;
 	Oid			relid;
 	Relation	rel;
+	LOCKMODE	lockmode = ShareUpdateExclusiveLock;
+
+	if (ddl_exclusive_locks)
+		lockmode = AccessExclusiveLock;
 
 	tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
 
@@ -1059,7 +1067,7 @@ RemoveTriggerById(Oid trigOid)
 	 */
 	relid = ((Form_pg_trigger) GETSTRUCT(tup))->tgrelid;
 
-	rel = heap_open(relid, AccessExclusiveLock);
+	rel = heap_open(relid, lockmode);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
 		rel->rd_rel->relkind != RELKIND_VIEW)
@@ -1225,8 +1233,8 @@ renametrig(RenameStmt *stmt)
 	 * on tgrelid/tgname would complain anyway) and to ensure a trigger does
 	 * exist with oldname.
 	 *
-	 * NOTE that this is cool only because we have AccessExclusiveLock on the
-	 * relation, so the trigger set won't be changing underneath us.
+	 * NOTE that this is cool only because we have a sufficient lock on the
+	 * relation to ensure that the trigger set won't be changing underneath us.
 	 */
 	tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
 
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2a46cfc..f0baf12 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -161,6 +161,14 @@ static bool eoxact_list_overflowed = false;
 			eoxact_list_overflowed = true; \
 	} while (0)
 
+/*
+ * EOXactTupleDescArray stores *TupleDescs that (might) need AtEOXact
+ * cleanup work.  The array expands as needed; there is no hashtable because
+ * we don't need to access individual items except at EOXact.
+ */
+static TupleDesc *EOXactTupleDescArray;
+static int NextEOXactTupleDescNum = 0;
+static int EOXactTupleDescArrayLen = 0;
 
 /*
  *		macros to manipulate the lookup hashtables
@@ -219,11 +227,12 @@ static HTAB *OpClassCache = NULL;
 
 /* non-export function prototypes */
 
-static void RelationDestroyRelation(Relation relation);
+static void RelationDestroyRelation(Relation relation, bool remember_tupdesc);
 static void RelationClearRelation(Relation relation, bool rebuild);
 
 static void RelationReloadIndexInfo(Relation relation);
 static void RelationFlushRelation(Relation relation);
+static void RememberToFreeTupleDescAtEOX(TupleDesc td);
 static void AtEOXact_cleanup(Relation relation, bool isCommit);
 static void AtEOSubXact_cleanup(Relation relation, bool isCommit,
 					SubTransactionId mySubid, SubTransactionId parentSubid);
@@ -1806,7 +1815,7 @@ RelationReloadIndexInfo(Relation relation)
  *	Caller must already have unhooked the entry from the hash table.
  */
 static void
-RelationDestroyRelation(Relation relation)
+RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 {
 	Assert(RelationHasReferenceCountZero(relation));
 
@@ -1826,7 +1835,12 @@ RelationDestroyRelation(Relation relation)
 	/* can't use DecrTupleDescRefCount here */
 	Assert(relation->rd_att->tdrefcount > 0);
 	if (--relation->rd_att->tdrefcount == 0)
-		FreeTupleDesc(relation->rd_att);
+	{
+		if (remember_tupdesc)
+			RememberToFreeTupleDescAtEOX(relation->rd_att);
+		else
+			FreeTupleDesc(relation->rd_att);
+	}
 	list_free(relation->rd_indexlist);
 	bms_free(relation->rd_indexattr);
 	FreeTriggerDesc(relation->trigdesc);
@@ -1940,7 +1954,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 		RelationCacheDelete(relation);
 
 		/* And release storage */
-		RelationDestroyRelation(relation);
+		RelationDestroyRelation(relation, false);
 	}
 	else
 	{
@@ -1984,7 +1998,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 		{
 			/* Should only get here if relation was deleted */
 			RelationCacheDelete(relation);
-			RelationDestroyRelation(relation);
+			RelationDestroyRelation(relation, false);
 			elog(ERROR, "relation %u deleted while still in use", save_relid);
 		}
 
@@ -2046,7 +2060,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 #undef SWAPFIELD
 
 		/* And now we can throw away the temporary entry */
-		RelationDestroyRelation(newrel);
+		RelationDestroyRelation(newrel, !keep_tupdesc);
 	}
 }
 
@@ -2283,6 +2297,37 @@ RelationCloseSmgrByOid(Oid relationId)
 	RelationCloseSmgr(relation);
 }
 
+void
+RememberToFreeTupleDescAtEOX(TupleDesc td)
+{
+	if (EOXactTupleDescArray == NULL)
+	{
+		MemoryContext	oldcxt;
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+		EOXactTupleDescArray = (TupleDesc *) palloc(16 * sizeof(TupleDesc));
+		EOXactTupleDescArrayLen = 16;
+		NextEOXactTupleDescNum = 0;
+		MemoryContextSwitchTo(oldcxt);
+	}
+	else if (NextEOXactTupleDescNum >= EOXactTupleDescArrayLen)
+	{
+		MemoryContext	oldcxt;
+		int32       	newlen = EOXactTupleDescArrayLen * 2;
+
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+		Assert(EOXactTupleDescArrayLen > 0);
+
+		EOXactTupleDescArray = (TupleDesc *) repalloc(EOXactTupleDescArray,
+														newlen * sizeof(TupleDesc));
+		EOXactTupleDescArrayLen = newlen;
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	EOXactTupleDescArray[NextEOXactTupleDescNum++] = td;
+}
+
 /*
  * AtEOXact_RelationCache
  *
@@ -2338,9 +2383,20 @@ AtEOXact_RelationCache(bool isCommit)
 		}
 	}
 
-	/* Now we're out of the transaction and can clear the list */
+	if (EOXactTupleDescArrayLen > 0)
+	{
+		Assert(EOXactTupleDescArray != NULL);
+		for (i = 0; i < NextEOXactTupleDescNum; i++)
+			FreeTupleDesc(EOXactTupleDescArray[i]);
+		pfree(EOXactTupleDescArray);
+		EOXactTupleDescArray = NULL;
+	}
+
+	/* Now we're out of the transaction and can clear the lists */
 	eoxact_list_len = 0;
 	eoxact_list_overflowed = false;
+	NextEOXactTupleDescNum = 0;
+	EOXactTupleDescArrayLen = 0;
 }
 
 /*
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 2cc8f90..6c548dc 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -1283,6 +1283,15 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
+		{"ddl_exclusive_locks", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS,
+			gettext_noop("Forces DDL statements to acquire AccessExclusiveLocks."),
+			NULL
+		},
+		&ddl_exclusive_locks,
+		false,
+		NULL, NULL, NULL
+	},
+	{
 		{"logging_collector", PGC_POSTMASTER, LOGGING_WHERE,
 			gettext_noop("Start a subprocess to capture stderr output and/or csvlogs into log files."),
 			NULL
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index f133e5f..f78db29 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -263,6 +263,7 @@ extern void PreventCommandDuringRecovery(const char *cmdname);
 /* in utils/misc/guc.c */
 extern int	trace_recovery_messages;
 extern int	trace_recovery(int trace_level);
+extern bool ddl_exclusive_locks;
 
 /*****************************************************************************
  *	  pdir.h --																 *
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 0f0c638..316d789 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1840,72 +1840,75 @@ and relnamespace != (select oid from pg_namespace where nspname = 'pg_catalog')
 and c.relname != 'my_locks'
 group by c.relname;
 create table alterlock (f1 int primary key, f2 text);
+insert into alterlock values (1, 'foo');
+create table alterlock2 (f3 int primary key, f1 int);
+insert into alterlock2 values (1, 1);
 begin; alter table alterlock alter column f2 set statistics 150;
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
 begin; alter table alterlock cluster on alterlock_pkey;
 select * from my_locks order by 1;
-    relname     |    max_lockmode     
-----------------+---------------------
- alterlock      | AccessExclusiveLock
- alterlock_pkey | AccessExclusiveLock
+    relname     |       max_lockmode       
+----------------+--------------------------
+ alterlock      | ShareUpdateExclusiveLock
+ alterlock_pkey | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set without cluster;
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 commit;
 begin; alter table alterlock set (fillfactor = 100);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock reset (fillfactor);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set (toast.autovacuum_enabled = off);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set (autovacuum_enabled = off);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock alter column f2 set (n_distinct = 1);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
@@ -1919,13 +1922,67 @@ select * from my_locks order by 1;
 rollback;
 begin; alter table alterlock alter column f2 set default 'x';
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |     max_lockmode      
+-----------+-----------------------
+ alterlock | ShareRowExclusiveLock
+(1 row)
+
+rollback;
+begin;
+create trigger ttdummy
+	before delete or update on alterlock
+	for each row
+	execute procedure
+	ttdummy (1, 1);
+select * from my_locks order by 1;
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
+begin;
+select * from my_locks order by 1;
+ relname | max_lockmode 
+---------+--------------
+(0 rows)
+
+alter table alterlock2 add foreign key (f1) references alterlock (f1);
+select * from my_locks order by 1;
+     relname     |       max_lockmode       
+-----------------+--------------------------
+ alterlock       | ShareUpdateExclusiveLock
+ alterlock2      | ShareRowExclusiveLock
+ alterlock2_pkey | AccessShareLock
+ alterlock_pkey  | AccessShareLock
+(4 rows)
+
+rollback;
+begin;
+alter table alterlock2
+add constraint alterlock2nv foreign key (f1) references alterlock (f1) NOT VALID;
+select * from my_locks order by 1;
+  relname   |       max_lockmode       
+------------+--------------------------
+ alterlock  | ShareUpdateExclusiveLock
+ alterlock2 | ShareRowExclusiveLock
+(2 rows)
+
+commit;
+begin;
+alter table alterlock2 validate constraint alterlock2nv;
+select * from my_locks order by 1;
+     relname     |       max_lockmode       
+-----------------+--------------------------
+ alterlock       | RowShareLock
+ alterlock2      | ShareUpdateExclusiveLock
+ alterlock2_pkey | AccessShareLock
+ alterlock_pkey  | AccessShareLock
+(4 rows)
+
+rollback;
 -- cleanup
+drop table alterlock2;
 drop table alterlock;
 drop view my_locks;
 drop type lockmodes;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 87973c1..a2ad863 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1283,6 +1283,9 @@ and c.relname != 'my_locks'
 group by c.relname;
 
 create table alterlock (f1 int primary key, f2 text);
+insert into alterlock values (1, 'foo');
+create table alterlock2 (f3 int primary key, f1 int);
+insert into alterlock2 values (1, 1);
 
 begin; alter table alterlock alter column f2 set statistics 150;
 select * from my_locks order by 1;
@@ -1324,7 +1327,33 @@ begin; alter table alterlock alter column f2 set default 'x';
 select * from my_locks order by 1;
 rollback;
 
+begin;
+create trigger ttdummy
+	before delete or update on alterlock
+	for each row
+	execute procedure
+	ttdummy (1, 1);
+select * from my_locks order by 1;
+rollback;
+
+begin;
+select * from my_locks order by 1;
+alter table alterlock2 add foreign key (f1) references alterlock (f1);
+select * from my_locks order by 1;
+rollback;
+
+begin;
+alter table alterlock2
+add constraint alterlock2nv foreign key (f1) references alterlock (f1) NOT VALID;
+select * from my_locks order by 1;
+commit;
+begin;
+alter table alterlock2 validate constraint alterlock2nv;
+select * from my_locks order by 1;
+rollback;
+
 -- cleanup
+drop table alterlock2;
 drop table alterlock;
 drop view my_locks;
 drop type lockmodes;
#15Robert Haas
robertmhaas@gmail.com
In reply to: Simon Riggs (#13)

On Mon, Jan 27, 2014 at 12:58 PM, Simon Riggs <simon@2ndquadrant.com> wrote:

On 24 January 2014 08:33, Simon Riggs <simon@2ndquadrant.com> wrote:

On 24 January 2014 07:08, Peter Geoghegan <pg@heroku.com> wrote:

On Wed, Jan 15, 2014 at 6:57 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

v15 to fix the above problem.

v16 attached

v17 attached

This version adds a GUC called ddl_exclusive_locks which allows us to
keep the 9.3 behaviour if we wish it. Some people may be surprised
that their programs don't wait in the same places they used to. We
hope that is a positive and useful behaviour, but it may not always be
so.

I'll commit this on Thurs 30 Jan unless I hear objections.

I haven't reviewed the patch, but -1 for adding a GUC.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#16Peter Geoghegan
pg@heroku.com
In reply to: Robert Haas (#15)

On Mon, Jan 27, 2014 at 12:25 PM, Robert Haas <robertmhaas@gmail.com> wrote:

I haven't reviewed the patch, but -1 for adding a GUC.

I'm pretty surprised that it's been suggested that some people might
prefer AccessExclusiveLocks. Why would anyone prefer that?

--
Peter Geoghegan

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#17Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Geoghegan (#16)

Peter Geoghegan <pg@heroku.com> writes:

On Mon, Jan 27, 2014 at 12:25 PM, Robert Haas <robertmhaas@gmail.com> wrote:

I haven't reviewed the patch, but -1 for adding a GUC.

I'm pretty surprised that it's been suggested that some people might
prefer AccessExclusiveLocks. Why would anyone prefer that?

For one thing, so they can back this out if it proves to be broken,
as the last committed version was. Given that this patch was marked
(by its author) as Ready for Committer without any review in the current
CF, I can't say that I have any faith in it.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#18Simon Riggs
simon@2ndQuadrant.com
In reply to: Peter Geoghegan (#16)

On 27 January 2014 20:35, Peter Geoghegan <pg@heroku.com> wrote:

On Mon, Jan 27, 2014 at 12:25 PM, Robert Haas <robertmhaas@gmail.com> wrote:

I haven't reviewed the patch, but -1 for adding a GUC.

I'm pretty surprised that it's been suggested that some people might
prefer AccessExclusiveLocks. Why would anyone prefer that?

Nobody has said "prefer". I said...

Some people may be surprised
that their programs don't wait in the same places they used to. We
hope that is a positive and useful behaviour, but it may not always be
so.

We get the new behaviour by default and I expect we'll be very happy with it.

A second thought is that if we have problems of some kind in the field
as a result of the new lock modes then we will be able to turn them
off. I'm happy to fix any problems that occur, but that doesn't mean
there won't be any. If everybody is confident that we've foreseen
every bug, then no problem, lets remove it. I recall being asked to
add hot_standby = on | off for similar reasons.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#19Simon Riggs
simon@2ndQuadrant.com
In reply to: Tom Lane (#17)

On 27 January 2014 20:47, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Peter Geoghegan <pg@heroku.com> writes:

On Mon, Jan 27, 2014 at 12:25 PM, Robert Haas <robertmhaas@gmail.com> wrote:

I haven't reviewed the patch, but -1 for adding a GUC.

I'm pretty surprised that it's been suggested that some people might
prefer AccessExclusiveLocks. Why would anyone prefer that?

For one thing, so they can back this out if it proves to be broken,
as the last committed version was.

Agreed

Given that this patch was marked
(by its author) as Ready for Committer without any review in the current
CF

True. The main review happened in a previous commitfest and there was
a small addition for this CF.

It was my understanding that you wanted us to indicate that to allow
you to review. I am happy to set status differently, as you wish,
presumably back to needs review.

I can't say that I have any faith in it.

That's a shame.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#20Bruce Momjian
bruce@momjian.us
In reply to: Simon Riggs (#18)

On Mon, Jan 27, 2014 at 08:57:26PM +0000, Simon Riggs wrote:

We get the new behaviour by default and I expect we'll be very happy with it.

A second thought is that if we have problems of some kind in the field
as a result of the new lock modes then we will be able to turn them
off. I'm happy to fix any problems that occur, but that doesn't mean
there won't be any. If everybody is confident that we've foreseen
every bug, then no problem, lets remove it. I recall being asked to
add hot_standby = on | off for similar reasons.

Addressing a larger issue, I have no problem with systematically adding
GUCs to turn off features we add in each major release if we can also
systematically remove those GUCs in the next major release.

This would require putting all these settings in the compatibility
section of postgresql.conf.

However, I don't think it makes sense to do this in a one-off manner.
It is also possible that there are enough cases where we _can't_ turn
the feature off with a GUC that this would be unworkable.

So, if we can't do it systematically, that means we will have enough
breakage cases that we just need to rush out new versions to fix major
breakage and one-off GUCs just don't buy us much, and add confusion.

Does that make sense?

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ Everyone has their own god. +

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#21Simon Riggs
simon@2ndQuadrant.com
In reply to: Bruce Momjian (#20)

On 28 January 2014 14:55, Bruce Momjian <bruce@momjian.us> wrote:

On Mon, Jan 27, 2014 at 08:57:26PM +0000, Simon Riggs wrote:

We get the new behaviour by default and I expect we'll be very happy with it.

A second thought is that if we have problems of some kind in the field
as a result of the new lock modes then we will be able to turn them
off. I'm happy to fix any problems that occur, but that doesn't mean
there won't be any. If everybody is confident that we've foreseen
every bug, then no problem, lets remove it. I recall being asked to
add hot_standby = on | off for similar reasons.

Addressing a larger issue, I have no problem with systematically adding
GUCs to turn off features we add in each major release if we can also
systematically remove those GUCs in the next major release.

Agreed. I propose 2 releases since the time between release of 9.4 and
the feature freeze of 9.5 is only 4 months, not usually enough for
subtle bugs to be discovered.

This would require putting all these settings in the compatibility
section of postgresql.conf.

Agreed, that is where I have added the parameter.

However, I don't think it makes sense to do this in a one-off manner.
It is also possible that there are enough cases where we _can't_ turn
the feature off with a GUC that this would be unworkable.

So, if we can't do it systematically, that means we will have enough
breakage cases that we just need to rush out new versions to fix major
breakage and one-off GUCs just don't buy us much, and add confusion.

Does that make sense?

For me, reducing the strength of DDL locking is a major change in
RDBMS behaviour that could both delight and surprise our users. Maybe
a few actually depend upon the locking behaviour, maybe. After some
years of various people looking at this, I think we've got it right.
Experience tells me that while I think this is the outcome, we are
well advised to protect against the possibility that it is not correct
and that if we have corner case issues, it would be good to easily
disable this in the field. In the current case, a simple parameter
works very well to disable the feature; in other cases, not.

Summary: This is an atypical case. I do not normally propose such
things - this is the third time in 10 years, IIRC.

I have no problem removing the parameter if required to. In that case,
I would like to leave the parameter in until mid beta, to allow
greater certainty. In any case, I would wish to retain as a minimum an
extern bool variable allowing it to be turned off by C function if
desired.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#22Bruce Momjian
bruce@momjian.us
In reply to: Simon Riggs (#21)

On Tue, Jan 28, 2014 at 04:36:39PM +0000, Simon Riggs wrote:

For me, reducing the strength of DDL locking is a major change in
RDBMS behaviour that could both delight and surprise our users. Maybe
a few actually depend upon the locking behaviour, maybe. After some
years of various people looking at this, I think we've got it right.
Experience tells me that while I think this is the outcome, we are
well advised to protect against the possibility that it is not correct
and that if we have corner case issues, it would be good to easily
disable this in the field. In the current case, a simple parameter
works very well to disable the feature; in other cases, not.

Summary: This is an atypical case. I do not normally propose such
things - this is the third time in 10 years, IIRC.

Uh, in my memory, you are the person who is most likely to suggest a
GUC of all our developers.

I have no problem removing the parameter if required to. In that case,
I would like to leave the parameter in until mid beta, to allow
greater certainty. In any case, I would wish to retain as a minimum an
extern bool variable allowing it to be turned off by C function if
desired.

Anything changed to postgresql.conf during beta is going to require an
initdb.

Also, lots of backward-compatibility infrastructure, as you are
suggesting above, add complexity to the system.

I am basically against a GUC on this. We have far larger problems with
9.3 than backward compatibility, and limited resources.

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ Everyone has their own god. +

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#23Heikki Linnakangas
hlinnakangas@vmware.com
In reply to: Bruce Momjian (#22)

On 01/28/2014 07:15 PM, Bruce Momjian wrote:

On Tue, Jan 28, 2014 at 04:36:39PM +0000, Simon Riggs wrote:

For me, reducing the strength of DDL locking is a major change in
RDBMS behaviour that could both delight and surprise our users. Maybe
a few actually depend upon the locking behaviour, maybe. After some
years of various people looking at this, I think we've got it right.
Experience tells me that while I think this is the outcome, we are
well advised to protect against the possibility that it is not correct
and that if we have corner case issues, it would be good to easily
disable this in the field. In the current case, a simple parameter
works very well to disable the feature; in other cases, not.

I don't understand why anyone would want to turn this feature off, ie.
require stronger locks than necessary for a DDL change.

If we're not confident that the patch is correct, then it should not be
applied. If we are confident enough to commit it, and a bug crops up
later, we'll fix the bug as usual.

A user savvy enough to fiddle with such a GUC can also do their DDL with:

BEGIN;
LOCK TABLE <table>
<DDL>
COMMIT;

I have no problem removing the parameter if required to. In that case,
I would like to leave the parameter in until mid beta, to allow
greater certainty. In any case, I would wish to retain as a minimum an
extern bool variable allowing it to be turned off by C function if
desired.

Anything changed to postgresql.conf during beta is going to require an
initdb.

Huh? Surely not.

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#24Bruce Momjian
bruce@momjian.us
In reply to: Heikki Linnakangas (#23)

On Tue, Jan 28, 2014 at 07:21:50PM +0200, Heikki Linnakangas wrote:

I have no problem removing the parameter if required to. In that case,
I would like to leave the parameter in until mid beta, to allow
greater certainty. In any case, I would wish to retain as a minimum an
extern bool variable allowing it to be turned off by C function if
desired.

Anything changed to postgresql.conf during beta is going to require an
initdb.

Huh? Surely not.

Uh, if we ship beta1 with a GUC in postgresql.conf, and then we remove
support for the GUC in beta2, anyone starting a server initdb-ed with
beta1 is going to get an error and the server is not going to start:

LOG: unrecognized configuration parameter "xxx" in file "/u/pgsql/data/postgresql.conf" line 1
FATAL: configuration file "/u/pgsql/data/postgresql.conf" contains errors

so, yeah, it isn't going to require an initdb, but it is going to
require everyone to edit their postgresql.conf. My guess is a lot of
people are going to assume the old postgresql.conf is not compatible and
are going to initdb and reload.

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ Everyone has their own god. +

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#25Heikki Linnakangas
hlinnakangas@vmware.com
In reply to: Bruce Momjian (#24)

On 01/28/2014 07:26 PM, Bruce Momjian wrote:

On Tue, Jan 28, 2014 at 07:21:50PM +0200, Heikki Linnakangas wrote:

I have no problem removing the parameter if required to. In that case,
I would like to leave the parameter in until mid beta, to allow
greater certainty. In any case, I would wish to retain as a minimum an
extern bool variable allowing it to be turned off by C function if
desired.

Anything changed to postgresql.conf during beta is going to require an
initdb.

Huh? Surely not.

Uh, if we ship beta1 with a GUC in postgresql.conf, and then we remove
support for the GUC in beta2, anyone starting a server initdb-ed with
beta1 is going to get an error and the server is not going to start:

LOG: unrecognized configuration parameter "xxx" in file "/u/pgsql/data/postgresql.conf" line 1
FATAL: configuration file "/u/pgsql/data/postgresql.conf" contains errors

so, yeah, it isn't going to require an initdb, but it is going to
require everyone to edit their postgresql.conf.

Only if you uncommented the value in the first place.

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#26Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Bruce Momjian (#22)

Bruce Momjian escribi�:

I have no problem removing the parameter if required to. In that case,
I would like to leave the parameter in until mid beta, to allow
greater certainty.

Uhm. If we remove a GUC during beta we don't need to force an initdb.
At worst we will need to keep a no-op GUC variable that is removed in
the next devel cycle. That said, if we're going to have a GUC that's
going to disappear later, I think it's better to wait for 2 releases as
proposed, not remove mid-beta.

In any case, I would wish to retain as a minimum an extern bool
variable allowing it to be turned off by C function if desired.

I think this amounts to having an undocumented GUC that is hard to
change. I prefer the GUC, myself.

Anything changed to postgresql.conf during beta is going to require an
initdb.
Also, lots of backward-compatibility infrastructure, as you are
suggesting above, add complexity to the system.

I am basically against a GUC on this. We have far larger problems with
9.3 than backward compatibility, and limited resources.

If we have a clear plan on removing the parameter, it's easy enough to
follow through. I don't think lack of resources is a good argument,
because at that point there will be little to discuss and it's an easy
change to make. And I think the plan is clear: if no bug is found the
parameter is removed. If a bug is found, it is fixed and the parameter
is removed anyway.

Honestly, I would prefer that we push a patch that has been thoroughly
reviewed and in which we have more confidence, so that we can push
without a GUC. This means mark in CF as needs-review, then some other
developer reviews it and marks it as ready-for-committer.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#27Bruce Momjian
bruce@momjian.us
In reply to: Heikki Linnakangas (#25)

On Tue, Jan 28, 2014 at 07:30:47PM +0200, Heikki Linnakangas wrote:

On 01/28/2014 07:26 PM, Bruce Momjian wrote:

On Tue, Jan 28, 2014 at 07:21:50PM +0200, Heikki Linnakangas wrote:

I have no problem removing the parameter if required to. In that case,
I would like to leave the parameter in until mid beta, to allow
greater certainty. In any case, I would wish to retain as a minimum an
extern bool variable allowing it to be turned off by C function if
desired.

Anything changed to postgresql.conf during beta is going to require an
initdb.

Huh? Surely not.

Uh, if we ship beta1 with a GUC in postgresql.conf, and then we remove
support for the GUC in beta2, anyone starting a server initdb-ed with
beta1 is going to get an error and the server is not going to start:

LOG: unrecognized configuration parameter "xxx" in file "/u/pgsql/data/postgresql.conf" line 1
FATAL: configuration file "/u/pgsql/data/postgresql.conf" contains errors

so, yeah, it isn't going to require an initdb, but it is going to
require everyone to edit their postgresql.conf.

Only if you uncommented the value in the first place.

Oh, I had forgotten that. Right. It would still be odd to have a
commented-out line in postgresql.conf that was not support.

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ Everyone has their own god. +

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#28Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#26)

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

Honestly, I would prefer that we push a patch that has been thoroughly
reviewed and in which we have more confidence, so that we can push
without a GUC. This means mark in CF as needs-review, then some other
developer reviews it and marks it as ready-for-committer.

FWIW, that was the point I was trying to make as well ;-)

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#29Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#15)

On 2014-01-27 15:25:22 -0500, Robert Haas wrote:

On Mon, Jan 27, 2014 at 12:58 PM, Simon Riggs <simon@2ndquadrant.com> wrote:

This version adds a GUC called ddl_exclusive_locks which allows us to
keep the 9.3 behaviour if we wish it. Some people may be surprised
that their programs don't wait in the same places they used to. We
hope that is a positive and useful behaviour, but it may not always be
so.

I'll commit this on Thurs 30 Jan unless I hear objections.

I haven't reviewed the patch, but -1 for adding a GUC.

Dito. I don't think the patch in a bad shape otherwise. I'd very quickly
looked at a previous version and it did look rather sane, and several
other people had looked at earlier versions as well. IIRC Noah had a
fairly extensive look at some intricate race conditions...

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#30Simon Riggs
simon@2ndQuadrant.com
In reply to: Bruce Momjian (#22)

On 28 January 2014 17:15, Bruce Momjian <bruce@momjian.us> wrote:

On Tue, Jan 28, 2014 at 04:36:39PM +0000, Simon Riggs wrote:

For me, reducing the strength of DDL locking is a major change in
RDBMS behaviour that could both delight and surprise our users. Maybe
a few actually depend upon the locking behaviour, maybe. After some
years of various people looking at this, I think we've got it right.
Experience tells me that while I think this is the outcome, we are
well advised to protect against the possibility that it is not correct
and that if we have corner case issues, it would be good to easily
disable this in the field. In the current case, a simple parameter
works very well to disable the feature; in other cases, not.

Summary: This is an atypical case. I do not normally propose such
things - this is the third time in 10 years, IIRC.

Uh, in my memory, you are the person who is most likely to suggest a
GUC of all our developers.

(smiles) I have suggested parameters for many features, mostly in the
early days of my developments before I saw the light if autotuning.
But those were control parameters for tuning features. So yes, I have
probably introduced more parameters than most, amongst the many things
I've done. I'm guessing you don't recall how much trouble I went to in
order to allow sync rep to have only 1 parameter, c'est la vie.

What I'm discussing here is a compatibility parameter to allow new
features to be disabled. This would be the third time in 10 years I
suggested a parameter for that reason, i.e. one that the user would
hardly ever wish to set.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#31Simon Riggs
simon@2ndQuadrant.com
In reply to: Heikki Linnakangas (#23)

On 28 January 2014 17:21, Heikki Linnakangas <hlinnakangas@vmware.com> wrote:

I don't understand why anyone would want to turn this feature off, ie.
require stronger locks than necessary for a DDL change.

Nobody would *want* to turn it off. They might need to, as explained.

If we're not confident that the patch is correct, then it should not be
applied. If we are confident enough to commit it, and a bug crops up later,
we'll fix the bug as usual.

I'd like to point out here that my own customers are already well
covered by the support services we offer. They will receive any such
fix very quickly.

My proposal was of assistance only to those without such contracts in
place, as are many of my proposals.

It doesn't bother me at all if you insist it should not be added. Just
choose v16 of the patch for review rather than v17.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#32Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#26)

On Tue, Jan 28, 2014 at 12:36 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Bruce Momjian escribió:

I have no problem removing the parameter if required to. In that case,
I would like to leave the parameter in until mid beta, to allow
greater certainty.

Uhm. If we remove a GUC during beta we don't need to force an initdb.
At worst we will need to keep a no-op GUC variable that is removed in
the next devel cycle. That said, if we're going to have a GUC that's
going to disappear later, I think it's better to wait for 2 releases as
proposed, not remove mid-beta.

In any case, I would wish to retain as a minimum an extern bool
variable allowing it to be turned off by C function if desired.

I think this amounts to having an undocumented GUC that is hard to
change. I prefer the GUC, myself.

Anything changed to postgresql.conf during beta is going to require an
initdb.
Also, lots of backward-compatibility infrastructure, as you are
suggesting above, add complexity to the system.

I am basically against a GUC on this. We have far larger problems with
9.3 than backward compatibility, and limited resources.

If we have a clear plan on removing the parameter, it's easy enough to
follow through. I don't think lack of resources is a good argument,
because at that point there will be little to discuss and it's an easy
change to make. And I think the plan is clear: if no bug is found the
parameter is removed. If a bug is found, it is fixed and the parameter
is removed anyway.

Honestly, I would prefer that we push a patch that has been thoroughly
reviewed and in which we have more confidence, so that we can push
without a GUC. This means mark in CF as needs-review, then some other
developer reviews it and marks it as ready-for-committer.

I also believe that would be the correct procedure. Personally, I
think it would be great if Noah can review this, as he's very good at
finding the kind of problems that are likely to crop up here, and has
examined previous versions. I also have some interest in looking at
it myself. But I doubt that either of us (or any other senior hacker)
can do that by Thursday. I think all such people are hip-deep in
other patches at the moment; I certainly am.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#33Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#32)

Robert Haas <robertmhaas@gmail.com> writes:

On Tue, Jan 28, 2014 at 12:36 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Honestly, I would prefer that we push a patch that has been thoroughly
reviewed and in which we have more confidence, so that we can push
without a GUC. This means mark in CF as needs-review, then some other
developer reviews it and marks it as ready-for-committer.

I also believe that would be the correct procedure. Personally, I
think it would be great if Noah can review this, as he's very good at
finding the kind of problems that are likely to crop up here, and has
examined previous versions. I also have some interest in looking at
it myself. But I doubt that either of us (or any other senior hacker)
can do that by Thursday. I think all such people are hip-deep in
other patches at the moment; I certainly am.

Yeah. I actually have little doubt that the patch is sane on its own
terms of discussion, that is that Simon has chosen locking levels that
are mutually consistent in an abstract sense. What sank the previous
iteration was that he'd failed to consider an implementation detail,
namely possible inconsistencies in SnapshotNow-based catalog fetches.
I'm afraid that there may be more such problems lurking under the
surface. Noah's pretty good at finding such things, but really two
or three of us need to sit down and think about it for awhile before
I'd have much confidence in such a fundamental change. And I sure don't
have time for this patch right now myself.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#34Simon Riggs
simon@2ndQuadrant.com
In reply to: Tom Lane (#33)

On 29 January 2014 05:43, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Tue, Jan 28, 2014 at 12:36 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Honestly, I would prefer that we push a patch that has been thoroughly
reviewed and in which we have more confidence, so that we can push
without a GUC. This means mark in CF as needs-review, then some other
developer reviews it and marks it as ready-for-committer.

I also believe that would be the correct procedure. Personally, I
think it would be great if Noah can review this, as he's very good at
finding the kind of problems that are likely to crop up here, and has
examined previous versions. I also have some interest in looking at
it myself. But I doubt that either of us (or any other senior hacker)
can do that by Thursday. I think all such people are hip-deep in
other patches at the moment; I certainly am.

Yeah. I actually have little doubt that the patch is sane on its own
terms of discussion, that is that Simon has chosen locking levels that
are mutually consistent in an abstract sense. What sank the previous
iteration was that he'd failed to consider an implementation detail,
namely possible inconsistencies in SnapshotNow-based catalog fetches.
I'm afraid that there may be more such problems lurking under the
surface.

Agreed

Noah's pretty good at finding such things, but really two
or three of us need to sit down and think about it for awhile before
I'd have much confidence in such a fundamental change. And I sure don't
have time for this patch right now myself.

I've reviewed Noah's earlier comments, fixed things and also further
reviewed for any similar relcache related issues.

I've also reviewed Hot Standby to see if any locking issues arise,
since the ALTER TABLE now won't generate an AccessExclusiveLock as it
used to do for certain operations. I can't see any problems there.

While doing those reviews, I'd remind everybody that this only affects
roughly half of ALTER TABLE subcommands and definitely nothing that
affects SELECTs. So the risk profile is much less than it sounds at
first glance.

If anybody else has any hints or clues about where to look, please
mention them and I will investigate. Thanks.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#35Andres Freund
andres@2ndquadrant.com
In reply to: Simon Riggs (#12)

Hi,

I took a quick peek at this, and noticed the following things:
* I am pretty sure this patch doesn't compile anymore after the latest
set of releases.
* This definitely should include isolationtester tests actually
performing concurrent ALTER TABLEs. All that's currently there is
tests that the locklevel isn't too high, but not that it actually works.
* So far no DDL uses ShareRowExclusiveLocks, why do so now? Not sure if
there aren't relevant cases for with foreign key checks (which afair
*do* acquire SRE locks).
* Why is AddConstraint "so complicated" when it's all used SRE locks?
* Are you sure AlterConstraint is generally safe without an AEL? It
should be safe to change whether something is deferred, but not
necessarily whether it's deferrable?
* Why does ChangeOwner need AEL?
* You changed several places to take out lower locks, which in itself is
fine, but doesn't that lead to lock upgrade hazard if a later step
acquires a stronger lock? Or do we take out a strong enough lock from
the beginning.
* There's no explanation why the EOXact TupleDesc stuff is needed?

That's it for now,

Andres

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#36Simon Riggs
simon@2ndQuadrant.com
In reply to: Andres Freund (#35)

On 24 February 2014 16:01, Andres Freund <andres@2ndquadrant.com> wrote:

Hi,

I took a quick peek at this, and noticed the following things:
* I am pretty sure this patch doesn't compile anymore after the latest
set of releases.

Refreshed to v18, will post shortly.

* This definitely should include isolationtester tests actually
performing concurrent ALTER TABLEs. All that's currently there is
tests that the locklevel isn't too high, but not that it actually works.

There is no concurrent behaviour here, hence no code that would be
exercised by concurrent tests.

Lock levels are proven in regression tests, so no further tests needed.

* So far no DDL uses ShareRowExclusiveLocks, why do so now? Not sure if
there aren't relevant cases for with foreign key checks (which afair
*do* acquire SRE locks).

That was discussed long ago. We can relax it more in the future, if
that is considered safe.

* Why is AddConstraint "so complicated" when it's all used SRE locks?

To ensure each case was considered, rather than just assume all cases
are the same.

* Are you sure AlterConstraint is generally safe without an AEL? It
should be safe to change whether something is deferred, but not
necessarily whether it's deferrable?

We change the lock levels for individual commands. This patch provides
some initial settings and infrastructure.

It is possible you are correct that changing the deferrability is not
safe without an AEL. That was enabled for the first time in this
release in a patch added by me after this patch was written. I will
think on that and change, if required.

* Why does ChangeOwner need AEL?

Ownership affects privileges, which includes SELECTs, hence AEL.

This is not a critical aspect of the patch.

* You changed several places to take out lower locks, which in itself is
fine, but doesn't that lead to lock upgrade hazard if a later step
acquires a stronger lock? Or do we take out a strong enough lock from
the beginning.

We asess the lock needed at parse time, then use it consistently
later. There is no lock upgrade hazard since that has been
specifically considered and removed.

* There's no explanation why the EOXact TupleDesc stuff is needed?

I will update comments.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#37Andres Freund
andres@2ndquadrant.com
In reply to: Simon Riggs (#36)

Hi,

On 2014-02-26 07:32:45 +0000, Simon Riggs wrote:

* This definitely should include isolationtester tests actually
performing concurrent ALTER TABLEs. All that's currently there is
tests that the locklevel isn't too high, but not that it actually works.

There is no concurrent behaviour here, hence no code that would be
exercised by concurrent tests.

Huh? There's most definitely new concurrent behaviour. Previously no
other backends could have a relation open (and locked) while it got
altered (which then sends out relcache invalidations). That's something
that should be tested.

* Why does ChangeOwner need AEL?

Ownership affects privileges, which includes SELECTs, hence AEL.

So?

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#38Simon Riggs
simon@2ndQuadrant.com
In reply to: Andres Freund (#37)

On 26 February 2014 13:38, Andres Freund <andres@2ndquadrant.com> wrote:

Hi,

On 2014-02-26 07:32:45 +0000, Simon Riggs wrote:

* This definitely should include isolationtester tests actually
performing concurrent ALTER TABLEs. All that's currently there is
tests that the locklevel isn't too high, but not that it actually works.

There is no concurrent behaviour here, hence no code that would be
exercised by concurrent tests.

Huh? There's most definitely new concurrent behaviour. Previously no
other backends could have a relation open (and locked) while it got
altered (which then sends out relcache invalidations). That's something
that should be tested.

It has been. High volume concurrent testing has been performed, per
Tom's original discussion upthread, but that's not part of the test
suite.
For other tests I have no guide as to how to write a set of automated
regression tests. Anything could cause a failure, so I'd need to write
an infinite set of tests to prove there is no bug *somewhere*. How
many tests are required? 0, 1, 3, 30?

* Why does ChangeOwner need AEL?

Ownership affects privileges, which includes SELECTs, hence AEL.

So?

That reply could be added to any post. Please explain your concern.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#39Andres Freund
andres@2ndquadrant.com
In reply to: Simon Riggs (#38)

On 2014-02-26 15:15:00 +0000, Simon Riggs wrote:

On 26 February 2014 13:38, Andres Freund <andres@2ndquadrant.com> wrote:

Hi,

On 2014-02-26 07:32:45 +0000, Simon Riggs wrote:

* This definitely should include isolationtester tests actually
performing concurrent ALTER TABLEs. All that's currently there is
tests that the locklevel isn't too high, but not that it actually works.

There is no concurrent behaviour here, hence no code that would be
exercised by concurrent tests.

Huh? There's most definitely new concurrent behaviour. Previously no
other backends could have a relation open (and locked) while it got
altered (which then sends out relcache invalidations). That's something
that should be tested.

It has been. High volume concurrent testing has been performed, per
Tom's original discussion upthread, but that's not part of the test
suite.

Yea, that's not what I am looking for.

For other tests I have no guide as to how to write a set of automated
regression tests. Anything could cause a failure, so I'd need to write
an infinite set of tests to prove there is no bug *somewhere*. How
many tests are required? 0, 1, 3, 30?

I think some isolationtester tests for the most important changes in
lock levels are appropriate. Say, create a PRIMARY KEY, DROP INHERIT,
... while a query is in progress in a nother session.

* Why does ChangeOwner need AEL?

Ownership affects privileges, which includes SELECTs, hence AEL.

So?

That reply could be added to any post. Please explain your concern.

I don't understand why that means it needs an AEL. After all,
e.g. changes in table inheritance do *not* require an AEL. I think it's
perfectly ok to not go for the minimally required locklevel for all
subcommands, but then it should be commented as such and not with
"change visible to SELECT" where other operations that do so as well
require lower locklevels.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#40Simon Riggs
simon@2ndQuadrant.com
In reply to: Simon Riggs (#36)

On 26 February 2014 07:32, Simon Riggs <simon@2ndquadrant.com> wrote:

* Are you sure AlterConstraint is generally safe without an AEL? It
should be safe to change whether something is deferred, but not
necessarily whether it's deferrable?

We change the lock levels for individual commands. This patch provides
some initial settings and infrastructure.

It is possible you are correct that changing the deferrability is not
safe without an AEL. That was enabled for the first time in this
release in a patch added by me after this patch was written. I will
think on that and change, if required.

Thoughts...

It would be a problem to change a DEFERRABLE constraint into a NOT
DEFERRABLE constraint concurrently with a transaction that was
currently deferring its constraint checks. It would not be a problem
to go in the other direction, relaxing the constraint attributes.

The patch uses ShareRowExclusive for AlterConstraint, so no writes are
possible concurrently with the table being ALTERed, hence the problem
situation cannot arise.

So in my understanding, no issue is possible.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#41Simon Riggs
simon@2ndQuadrant.com
In reply to: Andres Freund (#39)

On 26 February 2014 15:25, Andres Freund <andres@2ndquadrant.com> wrote:

* Why does ChangeOwner need AEL?

Ownership affects privileges, which includes SELECTs, hence AEL.

So?

That reply could be added to any post. Please explain your concern.

I don't understand why that means it needs an AEL. After all,
e.g. changes in table inheritance do *not* require an AEL. I think it's
perfectly ok to not go for the minimally required locklevel for all
subcommands, but then it should be commented as such and not with
"change visible to SELECT" where other operations that do so as well
require lower locklevels.

Those are two separate cases, with separate lock levels, so that
argument doesn't hold.

My understanding of the argument as to why Inheritance doesn't need
AEL is that adding/removing children is akin to inserting or deleting
rows from the parent.

Removing SELECT privilege while running a SELECT would be a different
matter. This is all a matter of definition; we can make up any rules
we like. Doing so is IMHO a separate patch and not something to hold
up the main patch.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#42Simon Riggs
simon@2ndQuadrant.com
In reply to: Andres Freund (#39)
1 attachment(s)

On 26 February 2014 15:25, Andres Freund <andres@2ndquadrant.com> wrote:

On 2014-02-26 15:15:00 +0000, Simon Riggs wrote:

On 26 February 2014 13:38, Andres Freund <andres@2ndquadrant.com> wrote:

Hi,

On 2014-02-26 07:32:45 +0000, Simon Riggs wrote:

* This definitely should include isolationtester tests actually
performing concurrent ALTER TABLEs. All that's currently there is
tests that the locklevel isn't too high, but not that it actually works.

There is no concurrent behaviour here, hence no code that would be
exercised by concurrent tests.

Huh? There's most definitely new concurrent behaviour. Previously no
other backends could have a relation open (and locked) while it got
altered (which then sends out relcache invalidations). That's something
that should be tested.

It has been. High volume concurrent testing has been performed, per
Tom's original discussion upthread, but that's not part of the test
suite.

Yea, that's not what I am looking for.

For other tests I have no guide as to how to write a set of automated
regression tests. Anything could cause a failure, so I'd need to write
an infinite set of tests to prove there is no bug *somewhere*. How
many tests are required? 0, 1, 3, 30?

I think some isolationtester tests for the most important changes in
lock levels are appropriate. Say, create a PRIMARY KEY, DROP INHERIT,
... while a query is in progress in a nother session.

OK, I'll work on some tests.

v18 attached, with v19 coming soon

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

reduce_lock_levels.v18.patchapplication/octet-stream; name=reduce_lock_levels.v18.patchDownload
diff --git a/doc/src/sgml/mvcc.sgml b/doc/src/sgml/mvcc.sgml
index 2ca423c..ccff611 100644
--- a/doc/src/sgml/mvcc.sgml
+++ b/doc/src/sgml/mvcc.sgml
@@ -865,7 +865,7 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         <para>
          Acquired by <command>VACUUM</command> (without <option>FULL</option>),
          <command>ANALYZE</>, <command>CREATE INDEX CONCURRENTLY</>, and
-         some forms of <command>ALTER TABLE</command>.
+         <command>ALTER TABLE VALIDATE</command>.
         </para>
        </listitem>
       </varlistentry>
@@ -906,8 +906,8 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         </para>
 
         <para>
-         This lock mode is not automatically acquired by any
-         <productname>PostgreSQL</productname> command.
+         Acquired by <command>ALTER TABLE</> for subcommand types that
+         affect write operations and by <command>CREATE TRIGGER</>.
         </para>
        </listitem>
       </varlistentry>
@@ -951,7 +951,7 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         </para>
 
         <para>
-         Acquired by the <command>ALTER TABLE</>, <command>DROP TABLE</>,
+         Acquired by the <command>ALTER TABLE</> for rewriting, <command>DROP TABLE</>,
          <command>TRUNCATE</command>, <command>REINDEX</command>,
          <command>CLUSTER</command>, and <command>VACUUM FULL</command>
          commands.
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 2b02e66..d314e51 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -84,7 +84,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
 
   <para>
    <command>ALTER TABLE</command> changes the definition of an existing table.
-   There are several subforms:
+   There are several subforms described below. Note that the lock level required
+   differs for each subform. An <literal>ACCESS EXCLUSIVE</literal> lock is held
+   unless explicitly noted. When multiple subcommands are listed, the lock
+   held will be the strictest one required from any subcommand.
 
   <variablelist>
    <varlistentry>
@@ -153,6 +156,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       or <command>UPDATE</> commands; they do not cause rows already in the
       table to change.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -181,6 +187,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <productname>PostgreSQL</productname> query planner, refer to
       <xref linkend="planner-stats">.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -213,6 +222,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       of statistics by the <productname>PostgreSQL</productname> query
       planner, refer to <xref linkend="planner-stats">.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -264,6 +276,12 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       the table, until it is validated by using the <literal>VALIDATE
       CONSTRAINT</literal> option.
      </para>
+     <para>
+      This form requires only a <literal>SHARE ROW EXCLUSIVE</literal> lock
+      on the table being altered. If the constraint is a foreign key then
+      a <literal>SHARE UPDATE EXCLUSIVE</literal> lock is also required on
+      the table referenced by the constraint.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -325,6 +343,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       This form alters the attributes of a constraint that was previously
       created. Currently only foreign key constraints may be altered.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -338,11 +359,17 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       Nothing happens if the constraint is already marked valid.
      </para>
      <para>
-      Validation can be a long process on larger tables and currently requires
-      an <literal>ACCESS EXCLUSIVE</literal> lock.  The value of separating
+      Validation can be a long process on larger tables. The value of separating
       validation from initial creation is that you can defer validation to less
       busy times, or can be used to give additional time to correct pre-existing
-      errors while preventing new errors.
+      errors while preventing new errors. Note also that validation on its own
+      does not prevent normal write commands against the table while it runs.
+     </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock
+      on the table being altered. If the constraint is a foreign key then
+      a <literal>ROW SHARE</literal> lock is also required on
+      the table referenced by the constraint.
      </para>
     </listitem>
    </varlistentry>
@@ -383,6 +410,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       mode, and triggers configured as <literal>ENABLE ALWAYS</literal> will
       fire regardless of the current replication mode.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -408,6 +438,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <xref linkend="SQL-CLUSTER">
       operations.  It does not actually re-cluster the table.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -420,6 +453,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       index specification from the table.  This affects
       future cluster operations that don't specify an index.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -467,6 +503,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       FULL</>, <xref linkend="SQL-CLUSTER"> or one of the forms
       of <command>ALTER TABLE</> that forces a table rewrite.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
 
      <note>
       <para>
@@ -489,6 +528,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       defaults.  As with <literal>SET</>, a table rewrite might be
       needed to update the table entirely.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -517,6 +559,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <literal>FOREIGN KEY</literal> constraints are not considered, but
       this might change in the future.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -529,6 +574,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       Queries against the parent table will no longer include records drawn
       from the target table.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -544,6 +592,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       that <command>CREATE TABLE OF</> would permit an equivalent table
       definition.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -553,6 +604,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
      <para>
       This form dissociates a typed table from its type.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1de3170..65257d5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2687,8 +2687,7 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
  * The caller must lock the relation, with an appropriate lock level
  * for the subcommands requested. Any subcommand that needs to rewrite
  * tuples in the table forces the whole command to be executed with
- * AccessExclusiveLock (actually, that is currently required always, but
- * we hope to relax it at some point).	We pass the lock level down
+ * AccessExclusiveLock.  We pass the lock level down
  * so that we can apply it recursively to inherited tables. Note that the
  * lock level we want as we recurse might well be higher than required for
  * that specific subcommand. So we pass down the overall lock requirement,
@@ -2755,30 +2754,8 @@ LOCKMODE
 AlterTableGetLockLevel(List *cmds)
 {
 	/*
-	 * Late in 9.1 dev cycle a number of issues were uncovered with access to
-	 * catalog relations, leading to the decision to re-enforce all DDL at
-	 * AccessExclusiveLock level by default.
-	 *
-	 * The issues are that there is a pervasive assumption in the code that
-	 * the catalogs will not be read unless an AccessExclusiveLock is held. If
-	 * that rule is relaxed, we must protect against a number of potential
-	 * effects - infrequent, but proven possible with test cases where
-	 * multiple DDL operations occur in a stream against frequently accessed
-	 * tables.
-	 *
-	 * 1. Catalog tables were read using SnapshotNow, which has a race bug that
-	 * allows a scan to return no valid rows even when one is present in the
-	 * case of a commit of a concurrent update of the catalog table.
-	 * SnapshotNow also ignores transactions in progress, so takes the latest
-	 * committed version without waiting for the latest changes.
-	 *
-	 * 2. Relcache needs to be internally consistent, so unless we lock the
-	 * definition during reads we have no way to guarantee that.
-	 *
-	 * 3. Catcache access isn't coordinated at all so refreshes can occur at
-	 * any time.
+	 * This only works if we read catalog tables using MVCC snapshots.
 	 */
-#ifdef REDUCED_ALTER_TABLE_LOCK_LEVELS
 	ListCell   *lcmd;
 	LOCKMODE	lockmode = ShareUpdateExclusiveLock;
 
@@ -2812,6 +2789,7 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_SetTableSpace:		/* must rewrite heap */
 			case AT_DropNotNull:		/* may change some SQL plans */
 			case AT_SetNotNull:
+			case AT_SetStorage:			/* may add toast tables, see ATRewriteCatalogs() */
 			case AT_GenericOptions:
 			case AT_AlterColumnGenericOptions:
 				cmd_lockmode = AccessExclusiveLock;
@@ -2832,6 +2810,7 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_DisableTrig:
 			case AT_DisableTrigAll:
 			case AT_DisableTrigUser:
+			case AT_AlterConstraint:
 			case AT_AddIndex:	/* from ADD CONSTRAINT */
 			case AT_AddIndexConstraint:
 			case AT_ReplicaIdentity:
@@ -2914,8 +2893,6 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_ReplaceRelOptions:
 			case AT_SetOptions:
 			case AT_ResetOptions:
-			case AT_SetStorage:
-			case AT_AlterConstraint:
 			case AT_ValidateConstraint:
 				cmd_lockmode = ShareUpdateExclusiveLock;
 				break;
@@ -2932,9 +2909,6 @@ AlterTableGetLockLevel(List *cmds)
 		if (cmd_lockmode > lockmode)
 			lockmode = cmd_lockmode;
 	}
-#else
-	LOCKMODE	lockmode = AccessExclusiveLock;
-#endif
 
 	return lockmode;
 }
@@ -3262,6 +3236,13 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
 		}
 	}
 
+	/*
+	 * If we think we might need to add/re-add toast tables then
+	 * we currently need to hold an AccessExclusiveLock.
+	 */
+	if (lockmode < AccessExclusiveLock)
+		return;
+
 	/* Check to see if a toast table must be added. */
 	foreach(ltab, *wqueue)
 	{
@@ -5870,9 +5851,9 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * deadlock.)
 	 */
 	if (OidIsValid(fkconstraint->old_pktable_oid))
-		pkrel = heap_open(fkconstraint->old_pktable_oid, AccessExclusiveLock);
+		pkrel = heap_open(fkconstraint->old_pktable_oid, ShareUpdateExclusiveLock);
 	else
-		pkrel = heap_openrv(fkconstraint->pktable, AccessExclusiveLock);
+		pkrel = heap_openrv(fkconstraint->pktable, ShareUpdateExclusiveLock);
 
 	/*
 	 * Validity checks (permission checks wait till we have the column
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index fa74bd2..a0ea474 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -156,9 +156,9 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 				referenced;
 
 	if (OidIsValid(relOid))
-		rel = heap_open(relOid, AccessExclusiveLock);
+		rel = heap_open(relOid, ShareUpdateExclusiveLock);
 	else
-		rel = heap_openrv(stmt->relation, AccessExclusiveLock);
+		rel = heap_openrv(stmt->relation, ShareUpdateExclusiveLock);
 
 	/*
 	 * Triggers must be on tables or views, and there are additional
@@ -500,8 +500,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * can skip this for internally generated triggers, since the name
 	 * modification above should be sufficient.
 	 *
-	 * NOTE that this is cool only because we have AccessExclusiveLock on the
-	 * relation, so the trigger set won't be changing underneath us.
+	 * NOTE that this is cool only because we have a sufficient lock on the
+	 * relation to ensure the trigger set won't be changing underneath us.
 	 */
 	if (!isInternal)
 	{
@@ -1077,7 +1077,7 @@ RemoveTriggerById(Oid trigOid)
 	 */
 	relid = ((Form_pg_trigger) GETSTRUCT(tup))->tgrelid;
 
-	rel = heap_open(relid, AccessExclusiveLock);
+	rel = heap_open(relid, ShareUpdateExclusiveLock);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
 		rel->rd_rel->relkind != RELKIND_VIEW)
@@ -1243,8 +1243,8 @@ renametrig(RenameStmt *stmt)
 	 * on tgrelid/tgname would complain anyway) and to ensure a trigger does
 	 * exist with oldname.
 	 *
-	 * NOTE that this is cool only because we have AccessExclusiveLock on the
-	 * relation, so the trigger set won't be changing underneath us.
+	 * NOTE that this is cool only because we have a sufficient lock on the
+	 * relation to ensure that the trigger set won't be changing underneath us.
 	 */
 	tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
 
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2810b35..b5eb96f 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -161,6 +161,14 @@ static bool eoxact_list_overflowed = false;
 			eoxact_list_overflowed = true; \
 	} while (0)
 
+/*
+ * EOXactTupleDescArray stores *TupleDescs that (might) need AtEOXact
+ * cleanup work.  The array expands as needed; there is no hashtable because
+ * we don't need to access individual items except at EOXact.
+ */
+static TupleDesc *EOXactTupleDescArray;
+static int NextEOXactTupleDescNum = 0;
+static int EOXactTupleDescArrayLen = 0;
 
 /*
  *		macros to manipulate the lookup hashtables
@@ -219,11 +227,12 @@ static HTAB *OpClassCache = NULL;
 
 /* non-export function prototypes */
 
-static void RelationDestroyRelation(Relation relation);
+static void RelationDestroyRelation(Relation relation, bool remember_tupdesc);
 static void RelationClearRelation(Relation relation, bool rebuild);
 
 static void RelationReloadIndexInfo(Relation relation);
 static void RelationFlushRelation(Relation relation);
+static void RememberToFreeTupleDescAtEOX(TupleDesc td);
 static void AtEOXact_cleanup(Relation relation, bool isCommit);
 static void AtEOSubXact_cleanup(Relation relation, bool isCommit,
 					SubTransactionId mySubid, SubTransactionId parentSubid);
@@ -1811,7 +1820,7 @@ RelationReloadIndexInfo(Relation relation)
  *	Caller must already have unhooked the entry from the hash table.
  */
 static void
-RelationDestroyRelation(Relation relation)
+RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 {
 	Assert(RelationHasReferenceCountZero(relation));
 
@@ -1831,7 +1840,12 @@ RelationDestroyRelation(Relation relation)
 	/* can't use DecrTupleDescRefCount here */
 	Assert(relation->rd_att->tdrefcount > 0);
 	if (--relation->rd_att->tdrefcount == 0)
-		FreeTupleDesc(relation->rd_att);
+	{
+		if (remember_tupdesc)
+			RememberToFreeTupleDescAtEOX(relation->rd_att);
+		else
+			FreeTupleDesc(relation->rd_att);
+	}
 	list_free(relation->rd_indexlist);
 	bms_free(relation->rd_indexattr);
 	FreeTriggerDesc(relation->trigdesc);
@@ -1945,7 +1959,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 		RelationCacheDelete(relation);
 
 		/* And release storage */
-		RelationDestroyRelation(relation);
+		RelationDestroyRelation(relation, false);
 	}
 	else if (!IsTransactionState())
 	{
@@ -2012,7 +2026,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 		{
 			/* Should only get here if relation was deleted */
 			RelationCacheDelete(relation);
-			RelationDestroyRelation(relation);
+			RelationDestroyRelation(relation, false);
 			elog(ERROR, "relation %u deleted while still in use", save_relid);
 		}
 
@@ -2074,7 +2088,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 #undef SWAPFIELD
 
 		/* And now we can throw away the temporary entry */
-		RelationDestroyRelation(newrel);
+		RelationDestroyRelation(newrel, !keep_tupdesc);
 	}
 }
 
@@ -2312,6 +2326,37 @@ RelationCloseSmgrByOid(Oid relationId)
 	RelationCloseSmgr(relation);
 }
 
+void
+RememberToFreeTupleDescAtEOX(TupleDesc td)
+{
+	if (EOXactTupleDescArray == NULL)
+	{
+		MemoryContext	oldcxt;
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+		EOXactTupleDescArray = (TupleDesc *) palloc(16 * sizeof(TupleDesc));
+		EOXactTupleDescArrayLen = 16;
+		NextEOXactTupleDescNum = 0;
+		MemoryContextSwitchTo(oldcxt);
+	}
+	else if (NextEOXactTupleDescNum >= EOXactTupleDescArrayLen)
+	{
+		MemoryContext	oldcxt;
+		int32       	newlen = EOXactTupleDescArrayLen * 2;
+
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+		Assert(EOXactTupleDescArrayLen > 0);
+
+		EOXactTupleDescArray = (TupleDesc *) repalloc(EOXactTupleDescArray,
+														newlen * sizeof(TupleDesc));
+		EOXactTupleDescArrayLen = newlen;
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	EOXactTupleDescArray[NextEOXactTupleDescNum++] = td;
+}
+
 /*
  * AtEOXact_RelationCache
  *
@@ -2367,9 +2412,20 @@ AtEOXact_RelationCache(bool isCommit)
 		}
 	}
 
-	/* Now we're out of the transaction and can clear the list */
+	if (EOXactTupleDescArrayLen > 0)
+	{
+		Assert(EOXactTupleDescArray != NULL);
+		for (i = 0; i < NextEOXactTupleDescNum; i++)
+			FreeTupleDesc(EOXactTupleDescArray[i]);
+		pfree(EOXactTupleDescArray);
+		EOXactTupleDescArray = NULL;
+	}
+
+	/* Now we're out of the transaction and can clear the lists */
 	eoxact_list_len = 0;
 	eoxact_list_overflowed = false;
+	NextEOXactTupleDescNum = 0;
+	EOXactTupleDescArrayLen = 0;
 }
 
 /*
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 0f0c638..316d789 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1840,72 +1840,75 @@ and relnamespace != (select oid from pg_namespace where nspname = 'pg_catalog')
 and c.relname != 'my_locks'
 group by c.relname;
 create table alterlock (f1 int primary key, f2 text);
+insert into alterlock values (1, 'foo');
+create table alterlock2 (f3 int primary key, f1 int);
+insert into alterlock2 values (1, 1);
 begin; alter table alterlock alter column f2 set statistics 150;
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
 begin; alter table alterlock cluster on alterlock_pkey;
 select * from my_locks order by 1;
-    relname     |    max_lockmode     
-----------------+---------------------
- alterlock      | AccessExclusiveLock
- alterlock_pkey | AccessExclusiveLock
+    relname     |       max_lockmode       
+----------------+--------------------------
+ alterlock      | ShareUpdateExclusiveLock
+ alterlock_pkey | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set without cluster;
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 commit;
 begin; alter table alterlock set (fillfactor = 100);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock reset (fillfactor);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set (toast.autovacuum_enabled = off);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set (autovacuum_enabled = off);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock alter column f2 set (n_distinct = 1);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
@@ -1919,13 +1922,67 @@ select * from my_locks order by 1;
 rollback;
 begin; alter table alterlock alter column f2 set default 'x';
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |     max_lockmode      
+-----------+-----------------------
+ alterlock | ShareRowExclusiveLock
+(1 row)
+
+rollback;
+begin;
+create trigger ttdummy
+	before delete or update on alterlock
+	for each row
+	execute procedure
+	ttdummy (1, 1);
+select * from my_locks order by 1;
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
+begin;
+select * from my_locks order by 1;
+ relname | max_lockmode 
+---------+--------------
+(0 rows)
+
+alter table alterlock2 add foreign key (f1) references alterlock (f1);
+select * from my_locks order by 1;
+     relname     |       max_lockmode       
+-----------------+--------------------------
+ alterlock       | ShareUpdateExclusiveLock
+ alterlock2      | ShareRowExclusiveLock
+ alterlock2_pkey | AccessShareLock
+ alterlock_pkey  | AccessShareLock
+(4 rows)
+
+rollback;
+begin;
+alter table alterlock2
+add constraint alterlock2nv foreign key (f1) references alterlock (f1) NOT VALID;
+select * from my_locks order by 1;
+  relname   |       max_lockmode       
+------------+--------------------------
+ alterlock  | ShareUpdateExclusiveLock
+ alterlock2 | ShareRowExclusiveLock
+(2 rows)
+
+commit;
+begin;
+alter table alterlock2 validate constraint alterlock2nv;
+select * from my_locks order by 1;
+     relname     |       max_lockmode       
+-----------------+--------------------------
+ alterlock       | RowShareLock
+ alterlock2      | ShareUpdateExclusiveLock
+ alterlock2_pkey | AccessShareLock
+ alterlock_pkey  | AccessShareLock
+(4 rows)
+
+rollback;
 -- cleanup
+drop table alterlock2;
 drop table alterlock;
 drop view my_locks;
 drop type lockmodes;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 87973c1..a2ad863 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1283,6 +1283,9 @@ and c.relname != 'my_locks'
 group by c.relname;
 
 create table alterlock (f1 int primary key, f2 text);
+insert into alterlock values (1, 'foo');
+create table alterlock2 (f3 int primary key, f1 int);
+insert into alterlock2 values (1, 1);
 
 begin; alter table alterlock alter column f2 set statistics 150;
 select * from my_locks order by 1;
@@ -1324,7 +1327,33 @@ begin; alter table alterlock alter column f2 set default 'x';
 select * from my_locks order by 1;
 rollback;
 
+begin;
+create trigger ttdummy
+	before delete or update on alterlock
+	for each row
+	execute procedure
+	ttdummy (1, 1);
+select * from my_locks order by 1;
+rollback;
+
+begin;
+select * from my_locks order by 1;
+alter table alterlock2 add foreign key (f1) references alterlock (f1);
+select * from my_locks order by 1;
+rollback;
+
+begin;
+alter table alterlock2
+add constraint alterlock2nv foreign key (f1) references alterlock (f1) NOT VALID;
+select * from my_locks order by 1;
+commit;
+begin;
+alter table alterlock2 validate constraint alterlock2nv;
+select * from my_locks order by 1;
+rollback;
+
 -- cleanup
+drop table alterlock2;
 drop table alterlock;
 drop view my_locks;
 drop type lockmodes;
#43Simon Riggs
simon@2ndQuadrant.com
In reply to: Simon Riggs (#42)
1 attachment(s)

On 27 February 2014 08:48, Simon Riggs <simon@2ndquadrant.com> wrote:

On 26 February 2014 15:25, Andres Freund <andres@2ndquadrant.com> wrote:

On 2014-02-26 15:15:00 +0000, Simon Riggs wrote:

On 26 February 2014 13:38, Andres Freund <andres@2ndquadrant.com> wrote:

Hi,

On 2014-02-26 07:32:45 +0000, Simon Riggs wrote:

* This definitely should include isolationtester tests actually
performing concurrent ALTER TABLEs. All that's currently there is
tests that the locklevel isn't too high, but not that it actually works.

There is no concurrent behaviour here, hence no code that would be
exercised by concurrent tests.

Huh? There's most definitely new concurrent behaviour. Previously no
other backends could have a relation open (and locked) while it got
altered (which then sends out relcache invalidations). That's something
that should be tested.

It has been. High volume concurrent testing has been performed, per
Tom's original discussion upthread, but that's not part of the test
suite.

Yea, that's not what I am looking for.

For other tests I have no guide as to how to write a set of automated
regression tests. Anything could cause a failure, so I'd need to write
an infinite set of tests to prove there is no bug *somewhere*. How
many tests are required? 0, 1, 3, 30?

I think some isolationtester tests for the most important changes in
lock levels are appropriate. Say, create a PRIMARY KEY, DROP INHERIT,
... while a query is in progress in a nother session.

OK, I'll work on some tests.

v18 attached, with v19 coming soon

v19 complete apart from requested comment additions

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

reduce_lock_levels.v19.patchapplication/octet-stream; name=reduce_lock_levels.v19.patchDownload
diff --git a/doc/src/sgml/mvcc.sgml b/doc/src/sgml/mvcc.sgml
index 2ca423c..ccff611 100644
--- a/doc/src/sgml/mvcc.sgml
+++ b/doc/src/sgml/mvcc.sgml
@@ -865,7 +865,7 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         <para>
          Acquired by <command>VACUUM</command> (without <option>FULL</option>),
          <command>ANALYZE</>, <command>CREATE INDEX CONCURRENTLY</>, and
-         some forms of <command>ALTER TABLE</command>.
+         <command>ALTER TABLE VALIDATE</command>.
         </para>
        </listitem>
       </varlistentry>
@@ -906,8 +906,8 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         </para>
 
         <para>
-         This lock mode is not automatically acquired by any
-         <productname>PostgreSQL</productname> command.
+         Acquired by <command>ALTER TABLE</> for subcommand types that
+         affect write operations and by <command>CREATE TRIGGER</>.
         </para>
        </listitem>
       </varlistentry>
@@ -951,7 +951,7 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         </para>
 
         <para>
-         Acquired by the <command>ALTER TABLE</>, <command>DROP TABLE</>,
+         Acquired by the <command>ALTER TABLE</> for rewriting, <command>DROP TABLE</>,
          <command>TRUNCATE</command>, <command>REINDEX</command>,
          <command>CLUSTER</command>, and <command>VACUUM FULL</command>
          commands.
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 2b02e66..d314e51 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -84,7 +84,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
 
   <para>
    <command>ALTER TABLE</command> changes the definition of an existing table.
-   There are several subforms:
+   There are several subforms described below. Note that the lock level required
+   differs for each subform. An <literal>ACCESS EXCLUSIVE</literal> lock is held
+   unless explicitly noted. When multiple subcommands are listed, the lock
+   held will be the strictest one required from any subcommand.
 
   <variablelist>
    <varlistentry>
@@ -153,6 +156,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       or <command>UPDATE</> commands; they do not cause rows already in the
       table to change.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -181,6 +187,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <productname>PostgreSQL</productname> query planner, refer to
       <xref linkend="planner-stats">.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -213,6 +222,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       of statistics by the <productname>PostgreSQL</productname> query
       planner, refer to <xref linkend="planner-stats">.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -264,6 +276,12 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       the table, until it is validated by using the <literal>VALIDATE
       CONSTRAINT</literal> option.
      </para>
+     <para>
+      This form requires only a <literal>SHARE ROW EXCLUSIVE</literal> lock
+      on the table being altered. If the constraint is a foreign key then
+      a <literal>SHARE UPDATE EXCLUSIVE</literal> lock is also required on
+      the table referenced by the constraint.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -325,6 +343,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       This form alters the attributes of a constraint that was previously
       created. Currently only foreign key constraints may be altered.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -338,11 +359,17 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       Nothing happens if the constraint is already marked valid.
      </para>
      <para>
-      Validation can be a long process on larger tables and currently requires
-      an <literal>ACCESS EXCLUSIVE</literal> lock.  The value of separating
+      Validation can be a long process on larger tables. The value of separating
       validation from initial creation is that you can defer validation to less
       busy times, or can be used to give additional time to correct pre-existing
-      errors while preventing new errors.
+      errors while preventing new errors. Note also that validation on its own
+      does not prevent normal write commands against the table while it runs.
+     </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock
+      on the table being altered. If the constraint is a foreign key then
+      a <literal>ROW SHARE</literal> lock is also required on
+      the table referenced by the constraint.
      </para>
     </listitem>
    </varlistentry>
@@ -383,6 +410,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       mode, and triggers configured as <literal>ENABLE ALWAYS</literal> will
       fire regardless of the current replication mode.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -408,6 +438,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <xref linkend="SQL-CLUSTER">
       operations.  It does not actually re-cluster the table.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -420,6 +453,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       index specification from the table.  This affects
       future cluster operations that don't specify an index.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -467,6 +503,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       FULL</>, <xref linkend="SQL-CLUSTER"> or one of the forms
       of <command>ALTER TABLE</> that forces a table rewrite.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
 
      <note>
       <para>
@@ -489,6 +528,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       defaults.  As with <literal>SET</>, a table rewrite might be
       needed to update the table entirely.
      </para>
+     <para>
+      This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -517,6 +559,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <literal>FOREIGN KEY</literal> constraints are not considered, but
       this might change in the future.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -529,6 +574,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       Queries against the parent table will no longer include records drawn
       from the target table.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -544,6 +592,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       that <command>CREATE TABLE OF</> would permit an equivalent table
       definition.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -553,6 +604,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
      <para>
       This form dissociates a typed table from its type.
      </para>
+     <para>
+      This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1de3170..65257d5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2687,8 +2687,7 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
  * The caller must lock the relation, with an appropriate lock level
  * for the subcommands requested. Any subcommand that needs to rewrite
  * tuples in the table forces the whole command to be executed with
- * AccessExclusiveLock (actually, that is currently required always, but
- * we hope to relax it at some point).	We pass the lock level down
+ * AccessExclusiveLock.  We pass the lock level down
  * so that we can apply it recursively to inherited tables. Note that the
  * lock level we want as we recurse might well be higher than required for
  * that specific subcommand. So we pass down the overall lock requirement,
@@ -2755,30 +2754,8 @@ LOCKMODE
 AlterTableGetLockLevel(List *cmds)
 {
 	/*
-	 * Late in 9.1 dev cycle a number of issues were uncovered with access to
-	 * catalog relations, leading to the decision to re-enforce all DDL at
-	 * AccessExclusiveLock level by default.
-	 *
-	 * The issues are that there is a pervasive assumption in the code that
-	 * the catalogs will not be read unless an AccessExclusiveLock is held. If
-	 * that rule is relaxed, we must protect against a number of potential
-	 * effects - infrequent, but proven possible with test cases where
-	 * multiple DDL operations occur in a stream against frequently accessed
-	 * tables.
-	 *
-	 * 1. Catalog tables were read using SnapshotNow, which has a race bug that
-	 * allows a scan to return no valid rows even when one is present in the
-	 * case of a commit of a concurrent update of the catalog table.
-	 * SnapshotNow also ignores transactions in progress, so takes the latest
-	 * committed version without waiting for the latest changes.
-	 *
-	 * 2. Relcache needs to be internally consistent, so unless we lock the
-	 * definition during reads we have no way to guarantee that.
-	 *
-	 * 3. Catcache access isn't coordinated at all so refreshes can occur at
-	 * any time.
+	 * This only works if we read catalog tables using MVCC snapshots.
 	 */
-#ifdef REDUCED_ALTER_TABLE_LOCK_LEVELS
 	ListCell   *lcmd;
 	LOCKMODE	lockmode = ShareUpdateExclusiveLock;
 
@@ -2812,6 +2789,7 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_SetTableSpace:		/* must rewrite heap */
 			case AT_DropNotNull:		/* may change some SQL plans */
 			case AT_SetNotNull:
+			case AT_SetStorage:			/* may add toast tables, see ATRewriteCatalogs() */
 			case AT_GenericOptions:
 			case AT_AlterColumnGenericOptions:
 				cmd_lockmode = AccessExclusiveLock;
@@ -2832,6 +2810,7 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_DisableTrig:
 			case AT_DisableTrigAll:
 			case AT_DisableTrigUser:
+			case AT_AlterConstraint:
 			case AT_AddIndex:	/* from ADD CONSTRAINT */
 			case AT_AddIndexConstraint:
 			case AT_ReplicaIdentity:
@@ -2914,8 +2893,6 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_ReplaceRelOptions:
 			case AT_SetOptions:
 			case AT_ResetOptions:
-			case AT_SetStorage:
-			case AT_AlterConstraint:
 			case AT_ValidateConstraint:
 				cmd_lockmode = ShareUpdateExclusiveLock;
 				break;
@@ -2932,9 +2909,6 @@ AlterTableGetLockLevel(List *cmds)
 		if (cmd_lockmode > lockmode)
 			lockmode = cmd_lockmode;
 	}
-#else
-	LOCKMODE	lockmode = AccessExclusiveLock;
-#endif
 
 	return lockmode;
 }
@@ -3262,6 +3236,13 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
 		}
 	}
 
+	/*
+	 * If we think we might need to add/re-add toast tables then
+	 * we currently need to hold an AccessExclusiveLock.
+	 */
+	if (lockmode < AccessExclusiveLock)
+		return;
+
 	/* Check to see if a toast table must be added. */
 	foreach(ltab, *wqueue)
 	{
@@ -5870,9 +5851,9 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * deadlock.)
 	 */
 	if (OidIsValid(fkconstraint->old_pktable_oid))
-		pkrel = heap_open(fkconstraint->old_pktable_oid, AccessExclusiveLock);
+		pkrel = heap_open(fkconstraint->old_pktable_oid, ShareUpdateExclusiveLock);
 	else
-		pkrel = heap_openrv(fkconstraint->pktable, AccessExclusiveLock);
+		pkrel = heap_openrv(fkconstraint->pktable, ShareUpdateExclusiveLock);
 
 	/*
 	 * Validity checks (permission checks wait till we have the column
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index fa74bd2..a0ea474 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -156,9 +156,9 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 				referenced;
 
 	if (OidIsValid(relOid))
-		rel = heap_open(relOid, AccessExclusiveLock);
+		rel = heap_open(relOid, ShareUpdateExclusiveLock);
 	else
-		rel = heap_openrv(stmt->relation, AccessExclusiveLock);
+		rel = heap_openrv(stmt->relation, ShareUpdateExclusiveLock);
 
 	/*
 	 * Triggers must be on tables or views, and there are additional
@@ -500,8 +500,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * can skip this for internally generated triggers, since the name
 	 * modification above should be sufficient.
 	 *
-	 * NOTE that this is cool only because we have AccessExclusiveLock on the
-	 * relation, so the trigger set won't be changing underneath us.
+	 * NOTE that this is cool only because we have a sufficient lock on the
+	 * relation to ensure the trigger set won't be changing underneath us.
 	 */
 	if (!isInternal)
 	{
@@ -1077,7 +1077,7 @@ RemoveTriggerById(Oid trigOid)
 	 */
 	relid = ((Form_pg_trigger) GETSTRUCT(tup))->tgrelid;
 
-	rel = heap_open(relid, AccessExclusiveLock);
+	rel = heap_open(relid, ShareUpdateExclusiveLock);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
 		rel->rd_rel->relkind != RELKIND_VIEW)
@@ -1243,8 +1243,8 @@ renametrig(RenameStmt *stmt)
 	 * on tgrelid/tgname would complain anyway) and to ensure a trigger does
 	 * exist with oldname.
 	 *
-	 * NOTE that this is cool only because we have AccessExclusiveLock on the
-	 * relation, so the trigger set won't be changing underneath us.
+	 * NOTE that this is cool only because we have a sufficient lock on the
+	 * relation to ensure that the trigger set won't be changing underneath us.
 	 */
 	tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
 
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2810b35..b5eb96f 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -161,6 +161,14 @@ static bool eoxact_list_overflowed = false;
 			eoxact_list_overflowed = true; \
 	} while (0)
 
+/*
+ * EOXactTupleDescArray stores *TupleDescs that (might) need AtEOXact
+ * cleanup work.  The array expands as needed; there is no hashtable because
+ * we don't need to access individual items except at EOXact.
+ */
+static TupleDesc *EOXactTupleDescArray;
+static int NextEOXactTupleDescNum = 0;
+static int EOXactTupleDescArrayLen = 0;
 
 /*
  *		macros to manipulate the lookup hashtables
@@ -219,11 +227,12 @@ static HTAB *OpClassCache = NULL;
 
 /* non-export function prototypes */
 
-static void RelationDestroyRelation(Relation relation);
+static void RelationDestroyRelation(Relation relation, bool remember_tupdesc);
 static void RelationClearRelation(Relation relation, bool rebuild);
 
 static void RelationReloadIndexInfo(Relation relation);
 static void RelationFlushRelation(Relation relation);
+static void RememberToFreeTupleDescAtEOX(TupleDesc td);
 static void AtEOXact_cleanup(Relation relation, bool isCommit);
 static void AtEOSubXact_cleanup(Relation relation, bool isCommit,
 					SubTransactionId mySubid, SubTransactionId parentSubid);
@@ -1811,7 +1820,7 @@ RelationReloadIndexInfo(Relation relation)
  *	Caller must already have unhooked the entry from the hash table.
  */
 static void
-RelationDestroyRelation(Relation relation)
+RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 {
 	Assert(RelationHasReferenceCountZero(relation));
 
@@ -1831,7 +1840,12 @@ RelationDestroyRelation(Relation relation)
 	/* can't use DecrTupleDescRefCount here */
 	Assert(relation->rd_att->tdrefcount > 0);
 	if (--relation->rd_att->tdrefcount == 0)
-		FreeTupleDesc(relation->rd_att);
+	{
+		if (remember_tupdesc)
+			RememberToFreeTupleDescAtEOX(relation->rd_att);
+		else
+			FreeTupleDesc(relation->rd_att);
+	}
 	list_free(relation->rd_indexlist);
 	bms_free(relation->rd_indexattr);
 	FreeTriggerDesc(relation->trigdesc);
@@ -1945,7 +1959,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 		RelationCacheDelete(relation);
 
 		/* And release storage */
-		RelationDestroyRelation(relation);
+		RelationDestroyRelation(relation, false);
 	}
 	else if (!IsTransactionState())
 	{
@@ -2012,7 +2026,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 		{
 			/* Should only get here if relation was deleted */
 			RelationCacheDelete(relation);
-			RelationDestroyRelation(relation);
+			RelationDestroyRelation(relation, false);
 			elog(ERROR, "relation %u deleted while still in use", save_relid);
 		}
 
@@ -2074,7 +2088,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 #undef SWAPFIELD
 
 		/* And now we can throw away the temporary entry */
-		RelationDestroyRelation(newrel);
+		RelationDestroyRelation(newrel, !keep_tupdesc);
 	}
 }
 
@@ -2312,6 +2326,37 @@ RelationCloseSmgrByOid(Oid relationId)
 	RelationCloseSmgr(relation);
 }
 
+void
+RememberToFreeTupleDescAtEOX(TupleDesc td)
+{
+	if (EOXactTupleDescArray == NULL)
+	{
+		MemoryContext	oldcxt;
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+		EOXactTupleDescArray = (TupleDesc *) palloc(16 * sizeof(TupleDesc));
+		EOXactTupleDescArrayLen = 16;
+		NextEOXactTupleDescNum = 0;
+		MemoryContextSwitchTo(oldcxt);
+	}
+	else if (NextEOXactTupleDescNum >= EOXactTupleDescArrayLen)
+	{
+		MemoryContext	oldcxt;
+		int32       	newlen = EOXactTupleDescArrayLen * 2;
+
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+		Assert(EOXactTupleDescArrayLen > 0);
+
+		EOXactTupleDescArray = (TupleDesc *) repalloc(EOXactTupleDescArray,
+														newlen * sizeof(TupleDesc));
+		EOXactTupleDescArrayLen = newlen;
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	EOXactTupleDescArray[NextEOXactTupleDescNum++] = td;
+}
+
 /*
  * AtEOXact_RelationCache
  *
@@ -2367,9 +2412,20 @@ AtEOXact_RelationCache(bool isCommit)
 		}
 	}
 
-	/* Now we're out of the transaction and can clear the list */
+	if (EOXactTupleDescArrayLen > 0)
+	{
+		Assert(EOXactTupleDescArray != NULL);
+		for (i = 0; i < NextEOXactTupleDescNum; i++)
+			FreeTupleDesc(EOXactTupleDescArray[i]);
+		pfree(EOXactTupleDescArray);
+		EOXactTupleDescArray = NULL;
+	}
+
+	/* Now we're out of the transaction and can clear the lists */
 	eoxact_list_len = 0;
 	eoxact_list_overflowed = false;
+	NextEOXactTupleDescNum = 0;
+	EOXactTupleDescArrayLen = 0;
 }
 
 /*
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 2b36e45..6685886 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -1947,7 +1947,7 @@ _discoverArchiveFormat(ArchiveHandle *AH)
 		else
 			AH->offSize = AH->intSize;
 
-		if ((AH->format = fgetc(fh)) == EOF)
+		if ((int)(AH->format = fgetc(fh)) == EOF)
 			exit_horribly(modulename, "could not read input file: %s\n", strerror(errno));
 		AH->lookahead[AH->lookaheadLen++] = AH->format;
 	}
diff --git a/src/test/isolation/expected/alter-table-1.out b/src/test/isolation/expected/alter-table-1.out
new file mode 100644
index 0000000..9daa418
--- /dev/null
+++ b/src/test/isolation/expected/alter-table-1.out
@@ -0,0 +1,3676 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1 at1 sc1 s2 at2 sc2 rx1 wx rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 at2 rx1 sc2 wx rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 at2 rx1 wx sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 at2 rx1 wx rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 at2 rx1 wx rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 at2 sc2 wx rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 at2 wx sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 at2 wx rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 at2 wx rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx at2 sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx at2 rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx at2 rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx rx1 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx rx1 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx rx1 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 at2 sc2 wx rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 at2 wx sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 at2 wx rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 at2 wx rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx at2 sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx at2 rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx at2 rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx rx1 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx rx1 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx rx1 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 at2 sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 at2 rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 at2 rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 rx1 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 rx1 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 rx1 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx rx1 s2 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx rx1 s2 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx rx1 s2 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx rx1 c2 s2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 at2 sc2 wx rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 at2 wx sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 at2 wx rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 at2 wx rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx at2 sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx at2 rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx at2 rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx rx1 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx rx1 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx rx1 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 at2 sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 at2 rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 at2 rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 rx1 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 rx1 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 rx1 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx rx1 s2 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx rx1 s2 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx rx1 s2 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx rx1 c2 s2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 s2 at2 sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 s2 at2 rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 s2 at2 rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 s2 rx1 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 s2 rx1 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 s2 rx1 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 rx1 s2 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 rx1 s2 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 rx1 s2 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 rx1 c2 s2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx rx1 sc1 s2 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 at1 rx1 wx rx1 sc1 s2 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 at1 rx1 wx rx1 sc1 s2 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 at1 rx1 wx rx1 sc1 c2 s2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 at1 rx1 wx rx1 c2 sc1 s2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 sc1 s2 at2 sc2 wx rx1 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 at2 wx sc2 rx1 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 at2 wx rx1 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 at2 wx rx1 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 wx at2 sc2 rx1 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 wx at2 rx1 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 wx at2 rx1 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 wx rx1 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 wx rx1 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 wx rx1 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx s2 at2 sc2 rx1 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx s2 at2 rx1 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx s2 at2 rx1 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx s2 rx1 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx s2 rx1 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx s2 rx1 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx rx1 s2 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx rx1 s2 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx rx1 s2 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx rx1 c2 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 s2 at2 sc2 rx1 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 s2 at2 rx1 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 s2 at2 rx1 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 s2 rx1 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 s2 rx1 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 s2 rx1 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 rx1 s2 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 rx1 s2 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 rx1 s2 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 rx1 c2 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx rx1 sc1 s2 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx rx1 sc1 s2 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx rx1 sc1 s2 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx rx1 sc1 c2 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx rx1 c2 sc1 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 s2 at2 sc2 rx1 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 s2 at2 rx1 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 s2 at2 rx1 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 s2 rx1 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 s2 rx1 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 s2 rx1 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 rx1 s2 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 rx1 s2 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 rx1 s2 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 rx1 c2 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 rx1 sc1 s2 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 rx1 sc1 s2 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 rx1 sc1 s2 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 rx1 sc1 c2 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 rx1 c2 sc1 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 wx rx1 at1 sc1 s2 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx rx1 at1 sc1 s2 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx rx1 at1 sc1 s2 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx rx1 at1 sc1 c2 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx rx1 at1 c2 sc1 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 wx rx1 c2 at1 sc1 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 at2 sc2 wx rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 at2 wx sc2 rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 at2 wx rx1 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 at2 wx rx1 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 wx at2 sc2 rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 wx at2 rx1 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 wx at2 rx1 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 wx rx1 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 wx rx1 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 wx rx1 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx s2 at2 sc2 rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx s2 at2 rx1 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx s2 at2 rx1 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx s2 rx1 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx s2 rx1 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx s2 rx1 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx rx1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx rx1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx rx1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx rx1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 s2 at2 sc2 rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 s2 at2 rx1 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 s2 at2 rx1 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 s2 rx1 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 s2 rx1 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 s2 rx1 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 rx1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 rx1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 rx1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 rx1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx rx1 sc1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx rx1 sc1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx rx1 sc1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx rx1 sc1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx rx1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 s2 at2 sc2 rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 s2 at2 rx1 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 s2 at2 rx1 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 s2 rx1 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 s2 rx1 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 s2 rx1 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 rx1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 rx1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 rx1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 rx1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 rx1 sc1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 rx1 sc1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 rx1 sc1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 rx1 sc1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 rx1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 wx rx1 at1 sc1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx rx1 at1 sc1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx rx1 at1 sc1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx rx1 at1 sc1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx rx1 at1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 wx rx1 c2 at1 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx s1 at1 sc1 s2 at2 sc2 rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 s2 at2 rx1 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 s2 at2 rx1 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 s2 rx1 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 s2 rx1 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 s2 rx1 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 rx1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 rx1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 rx1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 rx1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 rx1 sc1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 rx1 sc1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 rx1 sc1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 rx1 sc1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 rx1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx s1 rx1 at1 sc1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 rx1 at1 sc1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 rx1 at1 sc1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 rx1 at1 sc1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 rx1 at1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx s1 rx1 c2 at1 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx rx1 s1 at1 sc1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx rx1 s1 at1 sc1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx rx1 s1 at1 sc1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx rx1 s1 at1 sc1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx rx1 s1 at1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx rx1 s1 c2 at1 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s1: BEGIN;
+step c2: COMMIT;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx rx1 c2 s1 at1 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
diff --git a/src/test/isolation/expected/alter-table-2.out b/src/test/isolation/expected/alter-table-2.out
new file mode 100644
index 0000000..9e957bf
--- /dev/null
+++ b/src/test/isolation/expected/alter-table-2.out
@@ -0,0 +1,1681 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1 at1 c1 rx1 wx1 rx2 wx2 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+442368         
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+811008         
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 c1 wx1 rx2 wx2 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step c1: COMMIT;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+442368         
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+811008         
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx1 c1 rx2 wx2 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx1 rx2 c1 wx2 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx1 rx2 wx2 c1 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx1 rx2 wx2 rx3 c1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx1 rx2 wx2 rx3 c2 c1
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: s1 rx1 at1 c1 wx1 rx2 wx2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+442368         
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+811008         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx1 c1 rx2 wx2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx1 rx2 c1 wx2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx1 rx2 wx2 c1 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx1 rx2 wx2 rx3 c1 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx1 rx2 wx2 rx3 c2 c1
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: s1 rx1 wx1 at1 c1 rx2 wx2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 at1 rx2 c1 wx2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 at1 rx2 wx2 c1 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 at1 rx2 wx2 rx3 c1 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 at1 rx2 wx2 rx3 c2 c1
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 at1 c1 wx2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 at1 wx2 c1 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 at1 wx2 rx3 c1 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 at1 wx2 rx3 c2 c1
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 wx2 at1 c1 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 wx2 at1 rx3 c1 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 wx2 at1 rx3 c2 c1
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 wx2 rx3 at1 c1 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 wx2 rx3 at1 c2 c1
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 wx2 rx3 c2 at1 c1
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+
+starting permutation: rx1 s1 at1 c1 wx1 rx2 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+442368         
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+811008         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx1 c1 rx2 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx1 rx2 c1 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx1 rx2 wx2 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx1 rx2 wx2 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx1 rx2 wx2 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 s1 wx1 at1 c1 rx2 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 at1 rx2 c1 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 at1 rx2 wx2 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 at1 rx2 wx2 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 at1 rx2 wx2 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 at1 c1 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 at1 wx2 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 at1 wx2 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 at1 wx2 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 wx2 at1 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 wx2 at1 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 wx2 at1 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 wx2 rx3 at1 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 wx2 rx3 at1 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 wx2 rx3 c2 at1 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 s1 at1 c1 rx2 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 at1 rx2 c1 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 at1 rx2 wx2 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 at1 rx2 wx2 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 at1 rx2 wx2 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 at1 c1 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 at1 wx2 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 at1 wx2 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 at1 wx2 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 wx2 at1 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 wx2 at1 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 wx2 at1 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 wx2 rx3 at1 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 wx2 rx3 at1 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 wx2 rx3 c2 at1 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 at1 c1 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 at1 wx2 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 at1 wx2 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 at1 wx2 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 wx2 at1 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 wx2 at1 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 wx2 at1 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 wx2 rx3 at1 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 wx2 rx3 at1 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 wx2 rx3 c2 at1 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 s1 at1 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 s1 at1 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 s1 at1 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 s1 rx3 at1 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 s1 rx3 at1 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 s1 rx3 c2 at1 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 rx3 s1 at1 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 rx3 s1 at1 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 rx3 s1 c2 at1 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step s1: BEGIN;
+step c2: COMMIT;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 rx3 c2 s1 at1 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 1e73b4a..6fd2fe1 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -23,4 +23,6 @@ test: multixact-no-deadlock
 test: multixact-no-forget
 test: propagate-lock-delete
 test: drop-index-concurrently-1
+test: alter-table-1
+test: alter-table-2
 test: timeouts
diff --git a/src/test/isolation/specs/alter-table-1.spec b/src/test/isolation/specs/alter-table-1.spec
new file mode 100644
index 0000000..797f7b6
--- /dev/null
+++ b/src/test/isolation/specs/alter-table-1.spec
@@ -0,0 +1,30 @@
+# ALTER TABLE - Add and Validate constraint with concurrent writes
+#
+
+setup
+{
+ CREATE TABLE a (i int PRIMARY KEY);
+ CREATE TABLE b (a_id int);
+ INSERT INTO a VALUES (0), (1), (2), (3);
+ INSERT INTO b SELECT generate_series(1,1000) % 4;
+}
+
+teardown
+{
+ DROP TABLE a, b;
+}
+
+session "s1"
+step "s1"	{ BEGIN; }
+step "at1"	{ ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; }
+step "sc1"	{ COMMIT; }
+step "s2"	{ BEGIN; }
+step "at2"	{ ALTER TABLE b VALIDATE CONSTRAINT bfk; }
+step "sc2"	{ COMMIT; }
+
+session "s2"
+setup		{ BEGIN; }
+step "rx1"	{ SELECT * FROM b WHERE a_id = 1 LIMIT 1; }
+step "wx"	{ INSERT INTO b VALUES (0); }
+step "rx1"	{ SELECT * FROM b WHERE a_id = 3 LIMIT 3; }
+step "c2"	{ COMMIT; }
diff --git a/src/test/isolation/specs/alter-table-2.spec b/src/test/isolation/specs/alter-table-2.spec
new file mode 100644
index 0000000..2a5bb2d
--- /dev/null
+++ b/src/test/isolation/specs/alter-table-2.spec
@@ -0,0 +1,28 @@
+# ALTER TABLE - Change fillfactor
+#
+
+setup
+{
+ CREATE TABLE b (a_id int);
+ ALTER TABLE b SET (fillfactor = 100);
+ INSERT INTO b SELECT generate_series(1,1000) % 4;
+}
+
+teardown
+{
+ DROP TABLE b;
+}
+
+session "s1"
+step "s1"	{ BEGIN; }
+step "at1"	{ ALTER TABLE b SET (fillfactor = 10); }
+step "c1"	{ COMMIT; }
+
+session "s2"
+setup		{ BEGIN; }
+step "rx1"	{ SELECT pg_total_relation_size('b'); }
+step "wx1"	{ INSERT INTO b SELECT generate_series(1,1000) % 4; }
+step "rx2"	{ SELECT pg_total_relation_size('b'); }
+step "wx2"	{ INSERT INTO b SELECT generate_series(1,1000) % 4; }
+step "rx3"	{ SELECT pg_total_relation_size('b'); }
+step "c2"	{ COMMIT; }
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 0f0c638..316d789 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1840,72 +1840,75 @@ and relnamespace != (select oid from pg_namespace where nspname = 'pg_catalog')
 and c.relname != 'my_locks'
 group by c.relname;
 create table alterlock (f1 int primary key, f2 text);
+insert into alterlock values (1, 'foo');
+create table alterlock2 (f3 int primary key, f1 int);
+insert into alterlock2 values (1, 1);
 begin; alter table alterlock alter column f2 set statistics 150;
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
 begin; alter table alterlock cluster on alterlock_pkey;
 select * from my_locks order by 1;
-    relname     |    max_lockmode     
-----------------+---------------------
- alterlock      | AccessExclusiveLock
- alterlock_pkey | AccessExclusiveLock
+    relname     |       max_lockmode       
+----------------+--------------------------
+ alterlock      | ShareUpdateExclusiveLock
+ alterlock_pkey | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set without cluster;
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 commit;
 begin; alter table alterlock set (fillfactor = 100);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock reset (fillfactor);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set (toast.autovacuum_enabled = off);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set (autovacuum_enabled = off);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock alter column f2 set (n_distinct = 1);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
@@ -1919,13 +1922,67 @@ select * from my_locks order by 1;
 rollback;
 begin; alter table alterlock alter column f2 set default 'x';
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |     max_lockmode      
+-----------+-----------------------
+ alterlock | ShareRowExclusiveLock
+(1 row)
+
+rollback;
+begin;
+create trigger ttdummy
+	before delete or update on alterlock
+	for each row
+	execute procedure
+	ttdummy (1, 1);
+select * from my_locks order by 1;
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
+begin;
+select * from my_locks order by 1;
+ relname | max_lockmode 
+---------+--------------
+(0 rows)
+
+alter table alterlock2 add foreign key (f1) references alterlock (f1);
+select * from my_locks order by 1;
+     relname     |       max_lockmode       
+-----------------+--------------------------
+ alterlock       | ShareUpdateExclusiveLock
+ alterlock2      | ShareRowExclusiveLock
+ alterlock2_pkey | AccessShareLock
+ alterlock_pkey  | AccessShareLock
+(4 rows)
+
+rollback;
+begin;
+alter table alterlock2
+add constraint alterlock2nv foreign key (f1) references alterlock (f1) NOT VALID;
+select * from my_locks order by 1;
+  relname   |       max_lockmode       
+------------+--------------------------
+ alterlock  | ShareUpdateExclusiveLock
+ alterlock2 | ShareRowExclusiveLock
+(2 rows)
+
+commit;
+begin;
+alter table alterlock2 validate constraint alterlock2nv;
+select * from my_locks order by 1;
+     relname     |       max_lockmode       
+-----------------+--------------------------
+ alterlock       | RowShareLock
+ alterlock2      | ShareUpdateExclusiveLock
+ alterlock2_pkey | AccessShareLock
+ alterlock_pkey  | AccessShareLock
+(4 rows)
+
+rollback;
 -- cleanup
+drop table alterlock2;
 drop table alterlock;
 drop view my_locks;
 drop type lockmodes;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 87973c1..a2ad863 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1283,6 +1283,9 @@ and c.relname != 'my_locks'
 group by c.relname;
 
 create table alterlock (f1 int primary key, f2 text);
+insert into alterlock values (1, 'foo');
+create table alterlock2 (f3 int primary key, f1 int);
+insert into alterlock2 values (1, 1);
 
 begin; alter table alterlock alter column f2 set statistics 150;
 select * from my_locks order by 1;
@@ -1324,7 +1327,33 @@ begin; alter table alterlock alter column f2 set default 'x';
 select * from my_locks order by 1;
 rollback;
 
+begin;
+create trigger ttdummy
+	before delete or update on alterlock
+	for each row
+	execute procedure
+	ttdummy (1, 1);
+select * from my_locks order by 1;
+rollback;
+
+begin;
+select * from my_locks order by 1;
+alter table alterlock2 add foreign key (f1) references alterlock (f1);
+select * from my_locks order by 1;
+rollback;
+
+begin;
+alter table alterlock2
+add constraint alterlock2nv foreign key (f1) references alterlock (f1) NOT VALID;
+select * from my_locks order by 1;
+commit;
+begin;
+alter table alterlock2 validate constraint alterlock2nv;
+select * from my_locks order by 1;
+rollback;
+
 -- cleanup
+drop table alterlock2;
 drop table alterlock;
 drop view my_locks;
 drop type lockmodes;
#44Vik Fearing
vik.fearing@dalibo.com
In reply to: Simon Riggs (#43)
1 attachment(s)

On 03/01/2014 12:06 PM, Simon Riggs wrote:

On 27 February 2014 08:48, Simon Riggs <simon@2ndquadrant.com> wrote:

On 26 February 2014 15:25, Andres Freund <andres@2ndquadrant.com> wrote:

On 2014-02-26 15:15:00 +0000, Simon Riggs wrote:

On 26 February 2014 13:38, Andres Freund <andres@2ndquadrant.com> wrote:

Hi,

On 2014-02-26 07:32:45 +0000, Simon Riggs wrote:

* This definitely should include isolationtester tests actually
performing concurrent ALTER TABLEs. All that's currently there is
tests that the locklevel isn't too high, but not that it actually works.

There is no concurrent behaviour here, hence no code that would be
exercised by concurrent tests.

Huh? There's most definitely new concurrent behaviour. Previously no
other backends could have a relation open (and locked) while it got
altered (which then sends out relcache invalidations). That's something
that should be tested.

It has been. High volume concurrent testing has been performed, per
Tom's original discussion upthread, but that's not part of the test
suite.

Yea, that's not what I am looking for.

For other tests I have no guide as to how to write a set of automated
regression tests. Anything could cause a failure, so I'd need to write
an infinite set of tests to prove there is no bug *somewhere*. How
many tests are required? 0, 1, 3, 30?

I think some isolationtester tests for the most important changes in
lock levels are appropriate. Say, create a PRIMARY KEY, DROP INHERIT,
... while a query is in progress in a nother session.

OK, I'll work on some tests.

v18 attached, with v19 coming soon

v19 complete apart from requested comment additions

I've started to look at this patch and re-read the thread. The first
thing I noticed is what seems like an automated replace error. The docs
say "This form requires only an SHARE x EXCLUSIVE lock." where the "an"
was not changed to "a".

Attached is a patch-on-patch to fix this. A more complete review will
come later.

--
Vik

Attachments:

reduce_lock_levels.v19b.patchtext/x-diff; name=reduce_lock_levels.v19b.patchDownload
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
***************
*** 157,163 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
        table to change.
       </para>
       <para>
!       This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
       </para>
      </listitem>
     </varlistentry>
--- 157,163 ----
        table to change.
       </para>
       <para>
!       This form requires only a <literal>SHARE ROW EXCLUSIVE</literal> lock.
       </para>
      </listitem>
     </varlistentry>
***************
*** 188,194 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
        <xref linkend="planner-stats">.
       </para>
       <para>
!       This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
       </para>
      </listitem>
     </varlistentry>
--- 188,194 ----
        <xref linkend="planner-stats">.
       </para>
       <para>
!       This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
       </para>
      </listitem>
     </varlistentry>
***************
*** 223,229 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
        planner, refer to <xref linkend="planner-stats">.
       </para>
       <para>
!       This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
       </para>
      </listitem>
     </varlistentry>
--- 223,229 ----
        planner, refer to <xref linkend="planner-stats">.
       </para>
       <para>
!       This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
       </para>
      </listitem>
     </varlistentry>
***************
*** 344,350 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
        created. Currently only foreign key constraints may be altered.
       </para>
       <para>
!       This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
       </para>
      </listitem>
     </varlistentry>
--- 344,350 ----
        created. Currently only foreign key constraints may be altered.
       </para>
       <para>
!       This form requires only a <literal>SHARE ROW EXCLUSIVE</literal> lock.
       </para>
      </listitem>
     </varlistentry>
***************
*** 366,372 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
        does not prevent normal write commands against the table while it runs.
       </para>
       <para>
!       This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock
        on the table being altered. If the constraint is a foreign key then
        a <literal>ROW SHARE</literal> lock is also required on
        the table referenced by the constraint.
--- 366,372 ----
        does not prevent normal write commands against the table while it runs.
       </para>
       <para>
!       This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock
        on the table being altered. If the constraint is a foreign key then
        a <literal>ROW SHARE</literal> lock is also required on
        the table referenced by the constraint.
***************
*** 411,417 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
        fire regardless of the current replication mode.
       </para>
       <para>
!       This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
       </para>
      </listitem>
     </varlistentry>
--- 411,417 ----
        fire regardless of the current replication mode.
       </para>
       <para>
!       This form requires only a <literal>SHARE ROW EXCLUSIVE</literal> lock.
       </para>
      </listitem>
     </varlistentry>
***************
*** 439,445 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
        operations.  It does not actually re-cluster the table.
       </para>
       <para>
!       This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
       </para>
      </listitem>
     </varlistentry>
--- 439,445 ----
        operations.  It does not actually re-cluster the table.
       </para>
       <para>
!       This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
       </para>
      </listitem>
     </varlistentry>
***************
*** 454,460 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
        future cluster operations that don't specify an index.
       </para>
       <para>
!       This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
       </para>
      </listitem>
     </varlistentry>
--- 454,460 ----
        future cluster operations that don't specify an index.
       </para>
       <para>
!       This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
       </para>
      </listitem>
     </varlistentry>
***************
*** 504,510 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
        of <command>ALTER TABLE</> that forces a table rewrite.
       </para>
       <para>
!       This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
       </para>
  
       <note>
--- 504,510 ----
        of <command>ALTER TABLE</> that forces a table rewrite.
       </para>
       <para>
!       This form requires only a <literal>SHARE ROW EXCLUSIVE</literal> lock.
       </para>
  
       <note>
***************
*** 529,535 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
        needed to update the table entirely.
       </para>
       <para>
!       This form requires only an <literal>SHARE ROW EXCLUSIVE</literal> lock.
       </para>
      </listitem>
     </varlistentry>
--- 529,535 ----
        needed to update the table entirely.
       </para>
       <para>
!       This form requires only a <literal>SHARE ROW EXCLUSIVE</literal> lock.
       </para>
      </listitem>
     </varlistentry>
***************
*** 560,566 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
        this might change in the future.
       </para>
       <para>
!       This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
       </para>
      </listitem>
     </varlistentry>
--- 560,566 ----
        this might change in the future.
       </para>
       <para>
!       This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
       </para>
      </listitem>
     </varlistentry>
***************
*** 575,581 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
        from the target table.
       </para>
       <para>
!       This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
       </para>
      </listitem>
     </varlistentry>
--- 575,581 ----
        from the target table.
       </para>
       <para>
!       This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
       </para>
      </listitem>
     </varlistentry>
***************
*** 593,599 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
        definition.
       </para>
       <para>
!       This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
       </para>
      </listitem>
     </varlistentry>
--- 593,599 ----
        definition.
       </para>
       <para>
!       This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
       </para>
      </listitem>
     </varlistentry>
***************
*** 605,611 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
        This form dissociates a typed table from its type.
       </para>
       <para>
!       This form requires only an <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
       </para>
      </listitem>
     </varlistentry>
--- 605,611 ----
        This form dissociates a typed table from its type.
       </para>
       <para>
!       This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
       </para>
      </listitem>
     </varlistentry>
#45Simon Riggs
simon@2ndQuadrant.com
In reply to: Vik Fearing (#44)
1 attachment(s)

On 1 March 2014 21:25, Vik Fearing <vik.fearing@dalibo.com> wrote:

On 03/01/2014 12:06 PM, Simon Riggs wrote:

On 27 February 2014 08:48, Simon Riggs <simon@2ndquadrant.com> wrote:

On 26 February 2014 15:25, Andres Freund <andres@2ndquadrant.com> wrote:

On 2014-02-26 15:15:00 +0000, Simon Riggs wrote:

On 26 February 2014 13:38, Andres Freund <andres@2ndquadrant.com> wrote:

Hi,

On 2014-02-26 07:32:45 +0000, Simon Riggs wrote:

* This definitely should include isolationtester tests actually
performing concurrent ALTER TABLEs. All that's currently there is
tests that the locklevel isn't too high, but not that it actually works.

There is no concurrent behaviour here, hence no code that would be
exercised by concurrent tests.

Huh? There's most definitely new concurrent behaviour. Previously no
other backends could have a relation open (and locked) while it got
altered (which then sends out relcache invalidations). That's something
that should be tested.

It has been. High volume concurrent testing has been performed, per
Tom's original discussion upthread, but that's not part of the test
suite.

Yea, that's not what I am looking for.

For other tests I have no guide as to how to write a set of automated
regression tests. Anything could cause a failure, so I'd need to write
an infinite set of tests to prove there is no bug *somewhere*. How
many tests are required? 0, 1, 3, 30?

I think some isolationtester tests for the most important changes in
lock levels are appropriate. Say, create a PRIMARY KEY, DROP INHERIT,
... while a query is in progress in a nother session.

OK, I'll work on some tests.

v18 attached, with v19 coming soon

v19 complete apart from requested comment additions

I've started to look at this patch and re-read the thread. The first
thing I noticed is what seems like an automated replace error. The docs
say "This form requires only an SHARE x EXCLUSIVE lock." where the "an"
was not changed to "a".

Attached is a patch-on-patch to fix this. A more complete review will
come later.

v20 includes slightly re-ordered checks in GetLockLevel, plus more
detailed comments on each group of subcommands.

Also corrects grammar as noted by Vik.

Plus adds an example of usage to the docs.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

reduce_lock_levels.v20.patchapplication/octet-stream; name=reduce_lock_levels.v20.patchDownload
diff --git a/doc/src/sgml/mvcc.sgml b/doc/src/sgml/mvcc.sgml
index 2ca423c..ccff611 100644
--- a/doc/src/sgml/mvcc.sgml
+++ b/doc/src/sgml/mvcc.sgml
@@ -865,7 +865,7 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         <para>
          Acquired by <command>VACUUM</command> (without <option>FULL</option>),
          <command>ANALYZE</>, <command>CREATE INDEX CONCURRENTLY</>, and
-         some forms of <command>ALTER TABLE</command>.
+         <command>ALTER TABLE VALIDATE</command>.
         </para>
        </listitem>
       </varlistentry>
@@ -906,8 +906,8 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         </para>
 
         <para>
-         This lock mode is not automatically acquired by any
-         <productname>PostgreSQL</productname> command.
+         Acquired by <command>ALTER TABLE</> for subcommand types that
+         affect write operations and by <command>CREATE TRIGGER</>.
         </para>
        </listitem>
       </varlistentry>
@@ -951,7 +951,7 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         </para>
 
         <para>
-         Acquired by the <command>ALTER TABLE</>, <command>DROP TABLE</>,
+         Acquired by the <command>ALTER TABLE</> for rewriting, <command>DROP TABLE</>,
          <command>TRUNCATE</command>, <command>REINDEX</command>,
          <command>CLUSTER</command>, and <command>VACUUM FULL</command>
          commands.
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 2b02e66..3ce7f3c 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -84,7 +84,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
 
   <para>
    <command>ALTER TABLE</command> changes the definition of an existing table.
-   There are several subforms:
+   There are several subforms described below. Note that the lock level required
+   differs for each subform. An <literal>ACCESS EXCLUSIVE</literal> lock is held
+   unless explicitly noted. When multiple subcommands are listed, the lock
+   held will be the strictest one required from any subcommand.
 
   <variablelist>
    <varlistentry>
@@ -153,6 +156,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       or <command>UPDATE</> commands; they do not cause rows already in the
       table to change.
      </para>
+     <para>
+      This form requires only a <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -181,6 +187,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <productname>PostgreSQL</productname> query planner, refer to
       <xref linkend="planner-stats">.
      </para>
+     <para>
+      This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -213,6 +222,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       of statistics by the <productname>PostgreSQL</productname> query
       planner, refer to <xref linkend="planner-stats">.
      </para>
+     <para>
+      This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -264,6 +276,12 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       the table, until it is validated by using the <literal>VALIDATE
       CONSTRAINT</literal> option.
      </para>
+     <para>
+      This form requires only a <literal>SHARE ROW EXCLUSIVE</literal> lock
+      on the table being altered. If the constraint is a foreign key then
+      a <literal>SHARE UPDATE EXCLUSIVE</literal> lock is also required on
+      the table referenced by the constraint.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -325,6 +343,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       This form alters the attributes of a constraint that was previously
       created. Currently only foreign key constraints may be altered.
      </para>
+     <para>
+      This form requires only a <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -338,11 +359,17 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       Nothing happens if the constraint is already marked valid.
      </para>
      <para>
-      Validation can be a long process on larger tables and currently requires
-      an <literal>ACCESS EXCLUSIVE</literal> lock.  The value of separating
+      Validation can be a long process on larger tables. The value of separating
       validation from initial creation is that you can defer validation to less
       busy times, or can be used to give additional time to correct pre-existing
-      errors while preventing new errors.
+      errors while preventing new errors. Note also that validation on its own
+      does not prevent normal write commands against the table while it runs.
+     </para>
+     <para>
+      This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock
+      on the table being altered. If the constraint is a foreign key then
+      a <literal>ROW SHARE</literal> lock is also required on
+      the table referenced by the constraint.
      </para>
     </listitem>
    </varlistentry>
@@ -383,6 +410,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       mode, and triggers configured as <literal>ENABLE ALWAYS</literal> will
       fire regardless of the current replication mode.
      </para>
+     <para>
+      This form requires only a <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -408,6 +438,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <xref linkend="SQL-CLUSTER">
       operations.  It does not actually re-cluster the table.
      </para>
+     <para>
+      This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -420,6 +453,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       index specification from the table.  This affects
       future cluster operations that don't specify an index.
      </para>
+     <para>
+      This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -467,6 +503,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       FULL</>, <xref linkend="SQL-CLUSTER"> or one of the forms
       of <command>ALTER TABLE</> that forces a table rewrite.
      </para>
+     <para>
+      This form requires only a <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
 
      <note>
       <para>
@@ -489,6 +528,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       defaults.  As with <literal>SET</>, a table rewrite might be
       needed to update the table entirely.
      </para>
+     <para>
+      This form requires only a <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -517,6 +559,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <literal>FOREIGN KEY</literal> constraints are not considered, but
       this might change in the future.
      </para>
+     <para>
+      This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -529,6 +574,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       Queries against the parent table will no longer include records drawn
       from the target table.
      </para>
+     <para>
+      This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -544,6 +592,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       that <command>CREATE TABLE OF</> would permit an equivalent table
       definition.
      </para>
+     <para>
+      This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -553,6 +604,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
      <para>
       This form dissociates a typed table from its type.
      </para>
+     <para>
+      This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1de3170..fb23a31 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2687,8 +2687,7 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
  * The caller must lock the relation, with an appropriate lock level
  * for the subcommands requested. Any subcommand that needs to rewrite
  * tuples in the table forces the whole command to be executed with
- * AccessExclusiveLock (actually, that is currently required always, but
- * we hope to relax it at some point).	We pass the lock level down
+ * AccessExclusiveLock.  We pass the lock level down
  * so that we can apply it recursively to inherited tables. Note that the
  * lock level we want as we recurse might well be higher than required for
  * that specific subcommand. So we pass down the overall lock requirement,
@@ -2755,30 +2754,8 @@ LOCKMODE
 AlterTableGetLockLevel(List *cmds)
 {
 	/*
-	 * Late in 9.1 dev cycle a number of issues were uncovered with access to
-	 * catalog relations, leading to the decision to re-enforce all DDL at
-	 * AccessExclusiveLock level by default.
-	 *
-	 * The issues are that there is a pervasive assumption in the code that
-	 * the catalogs will not be read unless an AccessExclusiveLock is held. If
-	 * that rule is relaxed, we must protect against a number of potential
-	 * effects - infrequent, but proven possible with test cases where
-	 * multiple DDL operations occur in a stream against frequently accessed
-	 * tables.
-	 *
-	 * 1. Catalog tables were read using SnapshotNow, which has a race bug that
-	 * allows a scan to return no valid rows even when one is present in the
-	 * case of a commit of a concurrent update of the catalog table.
-	 * SnapshotNow also ignores transactions in progress, so takes the latest
-	 * committed version without waiting for the latest changes.
-	 *
-	 * 2. Relcache needs to be internally consistent, so unless we lock the
-	 * definition during reads we have no way to guarantee that.
-	 *
-	 * 3. Catcache access isn't coordinated at all so refreshes can occur at
-	 * any time.
+	 * This only works if we read catalog tables using MVCC snapshots.
 	 */
-#ifdef REDUCED_ALTER_TABLE_LOCK_LEVELS
 	ListCell   *lcmd;
 	LOCKMODE	lockmode = ShareUpdateExclusiveLock;
 
@@ -2790,27 +2767,55 @@ AlterTableGetLockLevel(List *cmds)
 		switch (cmd->subtype)
 		{
 				/*
-				 * Need AccessExclusiveLock for these subcommands because they
-				 * affect or potentially affect both read and write
-				 * operations.
-				 *
-				 * New subcommand types should be added here by default.
+				 * These subcommands rewrite the heap, so require full locks.
 				 */
 			case AT_AddColumn:	/* may rewrite heap, in some cases and visible
 								 * to SELECT */
-			case AT_DropColumn:	/* change visible to SELECT */
-			case AT_AddColumnToView:	/* CREATE VIEW */
+			case AT_SetTableSpace:		/* must rewrite heap */
 			case AT_AlterColumnType:	/* must rewrite heap */
+			case AT_AddOids:			/* must rewrite heap */
+				cmd_lockmode = AccessExclusiveLock;
+				break;
+
+				/*
+				 * These subcommands may require addition of toast tables.
+				 */
+			case AT_SetStorage:			/* may add toast tables, see ATRewriteCatalogs() */
+				cmd_lockmode = AccessExclusiveLock;
+				break;
+
+				/*
+				 * Removing constraints can affect SELECTs that have been
+				 * optimised assuming the constraint holds true.
+				 */
 			case AT_DropConstraint:		/* as DROP INDEX */
-			case AT_AddOids:	/* must rewrite heap */
-			case AT_DropOids:	/* calls AT_DropColumn */
+			case AT_DropNotNull:		/* may change some SQL plans */
+				cmd_lockmode = AccessExclusiveLock;
+				break;
+
+				/*
+				 * Subcommands that may be visible to concurrent SELECTs
+				 */
+			case AT_DropColumn:			/* change visible to SELECT */
+			case AT_AddColumnToView:	/* CREATE VIEW */
+			case AT_DropOids:			/* calls AT_DropColumn */
 			case AT_EnableAlwaysRule:	/* may change SELECT rules */
 			case AT_EnableReplicaRule:	/* may change SELECT rules */
-			case AT_EnableRule:	/* may change SELECT rules */
+			case AT_EnableRule:			/* may change SELECT rules */
 			case AT_DisableRule:		/* may change SELECT rules */
+				cmd_lockmode = AccessExclusiveLock;
+				break;
+
+				/*
+				 * Changing owner may remove implicit SELECT privileges
+				 */
 			case AT_ChangeOwner:		/* change visible to SELECT */
-			case AT_SetTableSpace:		/* must rewrite heap */
-			case AT_DropNotNull:		/* may change some SQL plans */
+				cmd_lockmode = AccessExclusiveLock;
+				break;
+
+				/*
+				 * Changing foreign table options may affect optimisation.
+				 */
 			case AT_SetNotNull:
 			case AT_GenericOptions:
 			case AT_AlterColumnGenericOptions:
@@ -2832,9 +2837,11 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_DisableTrig:
 			case AT_DisableTrigAll:
 			case AT_DisableTrigUser:
+			case AT_AlterConstraint:
 			case AT_AddIndex:	/* from ADD CONSTRAINT */
 			case AT_AddIndexConstraint:
 			case AT_ReplicaIdentity:
+			case AT_SetNotNull:
 				cmd_lockmode = ShareRowExclusiveLock;
 				break;
 
@@ -2914,8 +2921,6 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_ReplaceRelOptions:
 			case AT_SetOptions:
 			case AT_ResetOptions:
-			case AT_SetStorage:
-			case AT_AlterConstraint:
 			case AT_ValidateConstraint:
 				cmd_lockmode = ShareUpdateExclusiveLock;
 				break;
@@ -2932,9 +2937,6 @@ AlterTableGetLockLevel(List *cmds)
 		if (cmd_lockmode > lockmode)
 			lockmode = cmd_lockmode;
 	}
-#else
-	LOCKMODE	lockmode = AccessExclusiveLock;
-#endif
 
 	return lockmode;
 }
@@ -3262,6 +3264,13 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
 		}
 	}
 
+	/*
+	 * If we think we might need to add/re-add toast tables then
+	 * we currently need to hold an AccessExclusiveLock.
+	 */
+	if (lockmode < AccessExclusiveLock)
+		return;
+
 	/* Check to see if a toast table must be added. */
 	foreach(ltab, *wqueue)
 	{
@@ -5870,9 +5879,9 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * deadlock.)
 	 */
 	if (OidIsValid(fkconstraint->old_pktable_oid))
-		pkrel = heap_open(fkconstraint->old_pktable_oid, AccessExclusiveLock);
+		pkrel = heap_open(fkconstraint->old_pktable_oid, ShareUpdateExclusiveLock);
 	else
-		pkrel = heap_openrv(fkconstraint->pktable, AccessExclusiveLock);
+		pkrel = heap_openrv(fkconstraint->pktable, ShareUpdateExclusiveLock);
 
 	/*
 	 * Validity checks (permission checks wait till we have the column
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index fa74bd2..a0ea474 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -156,9 +156,9 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 				referenced;
 
 	if (OidIsValid(relOid))
-		rel = heap_open(relOid, AccessExclusiveLock);
+		rel = heap_open(relOid, ShareUpdateExclusiveLock);
 	else
-		rel = heap_openrv(stmt->relation, AccessExclusiveLock);
+		rel = heap_openrv(stmt->relation, ShareUpdateExclusiveLock);
 
 	/*
 	 * Triggers must be on tables or views, and there are additional
@@ -500,8 +500,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * can skip this for internally generated triggers, since the name
 	 * modification above should be sufficient.
 	 *
-	 * NOTE that this is cool only because we have AccessExclusiveLock on the
-	 * relation, so the trigger set won't be changing underneath us.
+	 * NOTE that this is cool only because we have a sufficient lock on the
+	 * relation to ensure the trigger set won't be changing underneath us.
 	 */
 	if (!isInternal)
 	{
@@ -1077,7 +1077,7 @@ RemoveTriggerById(Oid trigOid)
 	 */
 	relid = ((Form_pg_trigger) GETSTRUCT(tup))->tgrelid;
 
-	rel = heap_open(relid, AccessExclusiveLock);
+	rel = heap_open(relid, ShareUpdateExclusiveLock);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
 		rel->rd_rel->relkind != RELKIND_VIEW)
@@ -1243,8 +1243,8 @@ renametrig(RenameStmt *stmt)
 	 * on tgrelid/tgname would complain anyway) and to ensure a trigger does
 	 * exist with oldname.
 	 *
-	 * NOTE that this is cool only because we have AccessExclusiveLock on the
-	 * relation, so the trigger set won't be changing underneath us.
+	 * NOTE that this is cool only because we have a sufficient lock on the
+	 * relation to ensure that the trigger set won't be changing underneath us.
 	 */
 	tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
 
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2810b35..b5eb96f 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -161,6 +161,14 @@ static bool eoxact_list_overflowed = false;
 			eoxact_list_overflowed = true; \
 	} while (0)
 
+/*
+ * EOXactTupleDescArray stores *TupleDescs that (might) need AtEOXact
+ * cleanup work.  The array expands as needed; there is no hashtable because
+ * we don't need to access individual items except at EOXact.
+ */
+static TupleDesc *EOXactTupleDescArray;
+static int NextEOXactTupleDescNum = 0;
+static int EOXactTupleDescArrayLen = 0;
 
 /*
  *		macros to manipulate the lookup hashtables
@@ -219,11 +227,12 @@ static HTAB *OpClassCache = NULL;
 
 /* non-export function prototypes */
 
-static void RelationDestroyRelation(Relation relation);
+static void RelationDestroyRelation(Relation relation, bool remember_tupdesc);
 static void RelationClearRelation(Relation relation, bool rebuild);
 
 static void RelationReloadIndexInfo(Relation relation);
 static void RelationFlushRelation(Relation relation);
+static void RememberToFreeTupleDescAtEOX(TupleDesc td);
 static void AtEOXact_cleanup(Relation relation, bool isCommit);
 static void AtEOSubXact_cleanup(Relation relation, bool isCommit,
 					SubTransactionId mySubid, SubTransactionId parentSubid);
@@ -1811,7 +1820,7 @@ RelationReloadIndexInfo(Relation relation)
  *	Caller must already have unhooked the entry from the hash table.
  */
 static void
-RelationDestroyRelation(Relation relation)
+RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 {
 	Assert(RelationHasReferenceCountZero(relation));
 
@@ -1831,7 +1840,12 @@ RelationDestroyRelation(Relation relation)
 	/* can't use DecrTupleDescRefCount here */
 	Assert(relation->rd_att->tdrefcount > 0);
 	if (--relation->rd_att->tdrefcount == 0)
-		FreeTupleDesc(relation->rd_att);
+	{
+		if (remember_tupdesc)
+			RememberToFreeTupleDescAtEOX(relation->rd_att);
+		else
+			FreeTupleDesc(relation->rd_att);
+	}
 	list_free(relation->rd_indexlist);
 	bms_free(relation->rd_indexattr);
 	FreeTriggerDesc(relation->trigdesc);
@@ -1945,7 +1959,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 		RelationCacheDelete(relation);
 
 		/* And release storage */
-		RelationDestroyRelation(relation);
+		RelationDestroyRelation(relation, false);
 	}
 	else if (!IsTransactionState())
 	{
@@ -2012,7 +2026,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 		{
 			/* Should only get here if relation was deleted */
 			RelationCacheDelete(relation);
-			RelationDestroyRelation(relation);
+			RelationDestroyRelation(relation, false);
 			elog(ERROR, "relation %u deleted while still in use", save_relid);
 		}
 
@@ -2074,7 +2088,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 #undef SWAPFIELD
 
 		/* And now we can throw away the temporary entry */
-		RelationDestroyRelation(newrel);
+		RelationDestroyRelation(newrel, !keep_tupdesc);
 	}
 }
 
@@ -2312,6 +2326,37 @@ RelationCloseSmgrByOid(Oid relationId)
 	RelationCloseSmgr(relation);
 }
 
+void
+RememberToFreeTupleDescAtEOX(TupleDesc td)
+{
+	if (EOXactTupleDescArray == NULL)
+	{
+		MemoryContext	oldcxt;
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+		EOXactTupleDescArray = (TupleDesc *) palloc(16 * sizeof(TupleDesc));
+		EOXactTupleDescArrayLen = 16;
+		NextEOXactTupleDescNum = 0;
+		MemoryContextSwitchTo(oldcxt);
+	}
+	else if (NextEOXactTupleDescNum >= EOXactTupleDescArrayLen)
+	{
+		MemoryContext	oldcxt;
+		int32       	newlen = EOXactTupleDescArrayLen * 2;
+
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+		Assert(EOXactTupleDescArrayLen > 0);
+
+		EOXactTupleDescArray = (TupleDesc *) repalloc(EOXactTupleDescArray,
+														newlen * sizeof(TupleDesc));
+		EOXactTupleDescArrayLen = newlen;
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	EOXactTupleDescArray[NextEOXactTupleDescNum++] = td;
+}
+
 /*
  * AtEOXact_RelationCache
  *
@@ -2367,9 +2412,20 @@ AtEOXact_RelationCache(bool isCommit)
 		}
 	}
 
-	/* Now we're out of the transaction and can clear the list */
+	if (EOXactTupleDescArrayLen > 0)
+	{
+		Assert(EOXactTupleDescArray != NULL);
+		for (i = 0; i < NextEOXactTupleDescNum; i++)
+			FreeTupleDesc(EOXactTupleDescArray[i]);
+		pfree(EOXactTupleDescArray);
+		EOXactTupleDescArray = NULL;
+	}
+
+	/* Now we're out of the transaction and can clear the lists */
 	eoxact_list_len = 0;
 	eoxact_list_overflowed = false;
+	NextEOXactTupleDescNum = 0;
+	EOXactTupleDescArrayLen = 0;
 }
 
 /*
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 2b36e45..6685886 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -1947,7 +1947,7 @@ _discoverArchiveFormat(ArchiveHandle *AH)
 		else
 			AH->offSize = AH->intSize;
 
-		if ((AH->format = fgetc(fh)) == EOF)
+		if ((int)(AH->format = fgetc(fh)) == EOF)
 			exit_horribly(modulename, "could not read input file: %s\n", strerror(errno));
 		AH->lookahead[AH->lookaheadLen++] = AH->format;
 	}
diff --git a/src/test/isolation/expected/alter-table-1.out b/src/test/isolation/expected/alter-table-1.out
new file mode 100644
index 0000000..9daa418
--- /dev/null
+++ b/src/test/isolation/expected/alter-table-1.out
@@ -0,0 +1,3676 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1 at1 sc1 s2 at2 sc2 rx1 wx rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 at2 rx1 sc2 wx rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 at2 rx1 wx sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 at2 rx1 wx rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 at2 rx1 wx rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 at2 sc2 wx rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 at2 wx sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 at2 wx rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 at2 wx rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx at2 sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx at2 rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx at2 rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx rx1 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx rx1 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx rx1 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 at2 sc2 wx rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 at2 wx sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 at2 wx rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 at2 wx rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx at2 sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx at2 rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx at2 rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx rx1 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx rx1 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx rx1 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 at2 sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 at2 rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 at2 rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 rx1 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 rx1 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 rx1 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx rx1 s2 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx rx1 s2 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx rx1 s2 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx rx1 c2 s2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 at2 sc2 wx rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 at2 wx sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 at2 wx rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 at2 wx rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx at2 sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx at2 rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx at2 rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx rx1 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx rx1 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx rx1 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 at2 sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 at2 rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 at2 rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 rx1 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 rx1 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 rx1 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx rx1 s2 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx rx1 s2 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx rx1 s2 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx rx1 c2 s2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 s2 at2 sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 s2 at2 rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 s2 at2 rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 s2 rx1 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 s2 rx1 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 s2 rx1 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 rx1 s2 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 rx1 s2 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 rx1 s2 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 rx1 c2 s2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx rx1 sc1 s2 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 at1 rx1 wx rx1 sc1 s2 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 at1 rx1 wx rx1 sc1 s2 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 at1 rx1 wx rx1 sc1 c2 s2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 at1 rx1 wx rx1 c2 sc1 s2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 sc1 s2 at2 sc2 wx rx1 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 at2 wx sc2 rx1 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 at2 wx rx1 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 at2 wx rx1 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 wx at2 sc2 rx1 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 wx at2 rx1 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 wx at2 rx1 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 wx rx1 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 wx rx1 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 wx rx1 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx s2 at2 sc2 rx1 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx s2 at2 rx1 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx s2 at2 rx1 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx s2 rx1 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx s2 rx1 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx s2 rx1 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx rx1 s2 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx rx1 s2 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx rx1 s2 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx rx1 c2 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 s2 at2 sc2 rx1 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 s2 at2 rx1 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 s2 at2 rx1 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 s2 rx1 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 s2 rx1 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 s2 rx1 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 rx1 s2 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 rx1 s2 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 rx1 s2 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 rx1 c2 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx rx1 sc1 s2 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx rx1 sc1 s2 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx rx1 sc1 s2 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx rx1 sc1 c2 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx rx1 c2 sc1 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 s2 at2 sc2 rx1 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 s2 at2 rx1 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 s2 at2 rx1 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 s2 rx1 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 s2 rx1 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 s2 rx1 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 rx1 s2 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 rx1 s2 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 rx1 s2 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 rx1 c2 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 rx1 sc1 s2 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 rx1 sc1 s2 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 rx1 sc1 s2 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 rx1 sc1 c2 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 rx1 c2 sc1 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 wx rx1 at1 sc1 s2 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx rx1 at1 sc1 s2 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx rx1 at1 sc1 s2 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx rx1 at1 sc1 c2 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx rx1 at1 c2 sc1 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 wx rx1 c2 at1 sc1 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 at2 sc2 wx rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 at2 wx sc2 rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 at2 wx rx1 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 at2 wx rx1 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 wx at2 sc2 rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 wx at2 rx1 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 wx at2 rx1 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 wx rx1 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 wx rx1 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 wx rx1 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx s2 at2 sc2 rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx s2 at2 rx1 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx s2 at2 rx1 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx s2 rx1 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx s2 rx1 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx s2 rx1 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx rx1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx rx1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx rx1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx rx1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 s2 at2 sc2 rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 s2 at2 rx1 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 s2 at2 rx1 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 s2 rx1 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 s2 rx1 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 s2 rx1 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 rx1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 rx1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 rx1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 rx1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx rx1 sc1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx rx1 sc1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx rx1 sc1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx rx1 sc1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx rx1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 s2 at2 sc2 rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 s2 at2 rx1 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 s2 at2 rx1 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 s2 rx1 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 s2 rx1 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 s2 rx1 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 rx1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 rx1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 rx1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 rx1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 rx1 sc1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 rx1 sc1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 rx1 sc1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 rx1 sc1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 rx1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 wx rx1 at1 sc1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx rx1 at1 sc1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx rx1 at1 sc1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx rx1 at1 sc1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx rx1 at1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 wx rx1 c2 at1 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx s1 at1 sc1 s2 at2 sc2 rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 s2 at2 rx1 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 s2 at2 rx1 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 s2 rx1 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 s2 rx1 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 s2 rx1 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 rx1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 rx1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 rx1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 rx1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 rx1 sc1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 rx1 sc1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 rx1 sc1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 rx1 sc1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 rx1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx s1 rx1 at1 sc1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 rx1 at1 sc1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 rx1 at1 sc1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 rx1 at1 sc1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 rx1 at1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx s1 rx1 c2 at1 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx rx1 s1 at1 sc1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx rx1 s1 at1 sc1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx rx1 s1 at1 sc1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx rx1 s1 at1 sc1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx rx1 s1 at1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx rx1 s1 c2 at1 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s1: BEGIN;
+step c2: COMMIT;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx rx1 c2 s1 at1 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
diff --git a/src/test/isolation/expected/alter-table-2.out b/src/test/isolation/expected/alter-table-2.out
new file mode 100644
index 0000000..9e957bf
--- /dev/null
+++ b/src/test/isolation/expected/alter-table-2.out
@@ -0,0 +1,1681 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1 at1 c1 rx1 wx1 rx2 wx2 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+442368         
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+811008         
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 c1 wx1 rx2 wx2 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step c1: COMMIT;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+442368         
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+811008         
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx1 c1 rx2 wx2 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx1 rx2 c1 wx2 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx1 rx2 wx2 c1 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx1 rx2 wx2 rx3 c1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx1 rx2 wx2 rx3 c2 c1
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: s1 rx1 at1 c1 wx1 rx2 wx2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+442368         
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+811008         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx1 c1 rx2 wx2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx1 rx2 c1 wx2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx1 rx2 wx2 c1 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx1 rx2 wx2 rx3 c1 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx1 rx2 wx2 rx3 c2 c1
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: s1 rx1 wx1 at1 c1 rx2 wx2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 at1 rx2 c1 wx2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 at1 rx2 wx2 c1 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 at1 rx2 wx2 rx3 c1 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 at1 rx2 wx2 rx3 c2 c1
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 at1 c1 wx2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 at1 wx2 c1 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 at1 wx2 rx3 c1 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 at1 wx2 rx3 c2 c1
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 wx2 at1 c1 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 wx2 at1 rx3 c1 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 wx2 at1 rx3 c2 c1
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 wx2 rx3 at1 c1 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 wx2 rx3 at1 c2 c1
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 wx2 rx3 c2 at1 c1
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+
+starting permutation: rx1 s1 at1 c1 wx1 rx2 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+442368         
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+811008         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx1 c1 rx2 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx1 rx2 c1 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx1 rx2 wx2 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx1 rx2 wx2 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx1 rx2 wx2 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 s1 wx1 at1 c1 rx2 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 at1 rx2 c1 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 at1 rx2 wx2 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 at1 rx2 wx2 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 at1 rx2 wx2 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 at1 c1 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 at1 wx2 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 at1 wx2 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 at1 wx2 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 wx2 at1 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 wx2 at1 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 wx2 at1 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 wx2 rx3 at1 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 wx2 rx3 at1 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 wx2 rx3 c2 at1 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 s1 at1 c1 rx2 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 at1 rx2 c1 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 at1 rx2 wx2 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 at1 rx2 wx2 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 at1 rx2 wx2 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 at1 c1 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 at1 wx2 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 at1 wx2 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 at1 wx2 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 wx2 at1 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 wx2 at1 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 wx2 at1 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 wx2 rx3 at1 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 wx2 rx3 at1 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 wx2 rx3 c2 at1 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 at1 c1 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 at1 wx2 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 at1 wx2 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 at1 wx2 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 wx2 at1 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 wx2 at1 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 wx2 at1 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 wx2 rx3 at1 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 wx2 rx3 at1 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 wx2 rx3 c2 at1 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 s1 at1 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 s1 at1 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 s1 at1 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 s1 rx3 at1 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 s1 rx3 at1 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 s1 rx3 c2 at1 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 rx3 s1 at1 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 rx3 s1 at1 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 rx3 s1 c2 at1 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step s1: BEGIN;
+step c2: COMMIT;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 rx3 c2 s1 at1 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 1e73b4a..6fd2fe1 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -23,4 +23,6 @@ test: multixact-no-deadlock
 test: multixact-no-forget
 test: propagate-lock-delete
 test: drop-index-concurrently-1
+test: alter-table-1
+test: alter-table-2
 test: timeouts
diff --git a/src/test/isolation/specs/alter-table-1.spec b/src/test/isolation/specs/alter-table-1.spec
new file mode 100644
index 0000000..797f7b6
--- /dev/null
+++ b/src/test/isolation/specs/alter-table-1.spec
@@ -0,0 +1,30 @@
+# ALTER TABLE - Add and Validate constraint with concurrent writes
+#
+
+setup
+{
+ CREATE TABLE a (i int PRIMARY KEY);
+ CREATE TABLE b (a_id int);
+ INSERT INTO a VALUES (0), (1), (2), (3);
+ INSERT INTO b SELECT generate_series(1,1000) % 4;
+}
+
+teardown
+{
+ DROP TABLE a, b;
+}
+
+session "s1"
+step "s1"	{ BEGIN; }
+step "at1"	{ ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; }
+step "sc1"	{ COMMIT; }
+step "s2"	{ BEGIN; }
+step "at2"	{ ALTER TABLE b VALIDATE CONSTRAINT bfk; }
+step "sc2"	{ COMMIT; }
+
+session "s2"
+setup		{ BEGIN; }
+step "rx1"	{ SELECT * FROM b WHERE a_id = 1 LIMIT 1; }
+step "wx"	{ INSERT INTO b VALUES (0); }
+step "rx1"	{ SELECT * FROM b WHERE a_id = 3 LIMIT 3; }
+step "c2"	{ COMMIT; }
diff --git a/src/test/isolation/specs/alter-table-2.spec b/src/test/isolation/specs/alter-table-2.spec
new file mode 100644
index 0000000..2a5bb2d
--- /dev/null
+++ b/src/test/isolation/specs/alter-table-2.spec
@@ -0,0 +1,28 @@
+# ALTER TABLE - Change fillfactor
+#
+
+setup
+{
+ CREATE TABLE b (a_id int);
+ ALTER TABLE b SET (fillfactor = 100);
+ INSERT INTO b SELECT generate_series(1,1000) % 4;
+}
+
+teardown
+{
+ DROP TABLE b;
+}
+
+session "s1"
+step "s1"	{ BEGIN; }
+step "at1"	{ ALTER TABLE b SET (fillfactor = 10); }
+step "c1"	{ COMMIT; }
+
+session "s2"
+setup		{ BEGIN; }
+step "rx1"	{ SELECT pg_total_relation_size('b'); }
+step "wx1"	{ INSERT INTO b SELECT generate_series(1,1000) % 4; }
+step "rx2"	{ SELECT pg_total_relation_size('b'); }
+step "wx2"	{ INSERT INTO b SELECT generate_series(1,1000) % 4; }
+step "rx3"	{ SELECT pg_total_relation_size('b'); }
+step "c2"	{ COMMIT; }
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 0f0c638..316d789 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1840,72 +1840,75 @@ and relnamespace != (select oid from pg_namespace where nspname = 'pg_catalog')
 and c.relname != 'my_locks'
 group by c.relname;
 create table alterlock (f1 int primary key, f2 text);
+insert into alterlock values (1, 'foo');
+create table alterlock2 (f3 int primary key, f1 int);
+insert into alterlock2 values (1, 1);
 begin; alter table alterlock alter column f2 set statistics 150;
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
 begin; alter table alterlock cluster on alterlock_pkey;
 select * from my_locks order by 1;
-    relname     |    max_lockmode     
-----------------+---------------------
- alterlock      | AccessExclusiveLock
- alterlock_pkey | AccessExclusiveLock
+    relname     |       max_lockmode       
+----------------+--------------------------
+ alterlock      | ShareUpdateExclusiveLock
+ alterlock_pkey | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set without cluster;
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 commit;
 begin; alter table alterlock set (fillfactor = 100);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock reset (fillfactor);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set (toast.autovacuum_enabled = off);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set (autovacuum_enabled = off);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock alter column f2 set (n_distinct = 1);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
@@ -1919,13 +1922,67 @@ select * from my_locks order by 1;
 rollback;
 begin; alter table alterlock alter column f2 set default 'x';
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |     max_lockmode      
+-----------+-----------------------
+ alterlock | ShareRowExclusiveLock
+(1 row)
+
+rollback;
+begin;
+create trigger ttdummy
+	before delete or update on alterlock
+	for each row
+	execute procedure
+	ttdummy (1, 1);
+select * from my_locks order by 1;
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
+begin;
+select * from my_locks order by 1;
+ relname | max_lockmode 
+---------+--------------
+(0 rows)
+
+alter table alterlock2 add foreign key (f1) references alterlock (f1);
+select * from my_locks order by 1;
+     relname     |       max_lockmode       
+-----------------+--------------------------
+ alterlock       | ShareUpdateExclusiveLock
+ alterlock2      | ShareRowExclusiveLock
+ alterlock2_pkey | AccessShareLock
+ alterlock_pkey  | AccessShareLock
+(4 rows)
+
+rollback;
+begin;
+alter table alterlock2
+add constraint alterlock2nv foreign key (f1) references alterlock (f1) NOT VALID;
+select * from my_locks order by 1;
+  relname   |       max_lockmode       
+------------+--------------------------
+ alterlock  | ShareUpdateExclusiveLock
+ alterlock2 | ShareRowExclusiveLock
+(2 rows)
+
+commit;
+begin;
+alter table alterlock2 validate constraint alterlock2nv;
+select * from my_locks order by 1;
+     relname     |       max_lockmode       
+-----------------+--------------------------
+ alterlock       | RowShareLock
+ alterlock2      | ShareUpdateExclusiveLock
+ alterlock2_pkey | AccessShareLock
+ alterlock_pkey  | AccessShareLock
+(4 rows)
+
+rollback;
 -- cleanup
+drop table alterlock2;
 drop table alterlock;
 drop view my_locks;
 drop type lockmodes;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 87973c1..a2ad863 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1283,6 +1283,9 @@ and c.relname != 'my_locks'
 group by c.relname;
 
 create table alterlock (f1 int primary key, f2 text);
+insert into alterlock values (1, 'foo');
+create table alterlock2 (f3 int primary key, f1 int);
+insert into alterlock2 values (1, 1);
 
 begin; alter table alterlock alter column f2 set statistics 150;
 select * from my_locks order by 1;
@@ -1324,7 +1327,33 @@ begin; alter table alterlock alter column f2 set default 'x';
 select * from my_locks order by 1;
 rollback;
 
+begin;
+create trigger ttdummy
+	before delete or update on alterlock
+	for each row
+	execute procedure
+	ttdummy (1, 1);
+select * from my_locks order by 1;
+rollback;
+
+begin;
+select * from my_locks order by 1;
+alter table alterlock2 add foreign key (f1) references alterlock (f1);
+select * from my_locks order by 1;
+rollback;
+
+begin;
+alter table alterlock2
+add constraint alterlock2nv foreign key (f1) references alterlock (f1) NOT VALID;
+select * from my_locks order by 1;
+commit;
+begin;
+alter table alterlock2 validate constraint alterlock2nv;
+select * from my_locks order by 1;
+rollback;
+
 -- cleanup
+drop table alterlock2;
 drop table alterlock;
 drop view my_locks;
 drop type lockmodes;
#46Robert Haas
robertmhaas@gmail.com
In reply to: Simon Riggs (#41)

On Thu, Feb 27, 2014 at 3:12 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

Removing SELECT privilege while running a SELECT would be a different
matter. This is all a matter of definition; we can make up any rules
we like. Doing so is IMHO a separate patch and not something to hold
up the main patch.

So I think this is an interesting point. There are various things
that could go wrong as a result of using the wrong lock level. Worst
would be that the server crashes or corrupts data. A little less bad
would be that sessions error out with inexplicable error conditions,
as in SnapshotNow days. Alternatively, we could just have arguably
wrong behavior, like basing query results on the old version of the
table's metadata even after it's been changed.

I don't really care about that second category of behavior. If
somebody changes some property of a table and existing sessions
continue to use the old value until eoxact, well, we can argue about
that, but at least until we have concrete reports of really
undesirable behavior, I don't think it's the primary issue. What I'm
really concerned about is whether there are other things like the
SnapshotNow issues that can cause stuff to halt and catch fire. I
don't know whether there are or are not, but that's my concern.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#47Simon Riggs
simon@2ndQuadrant.com
In reply to: Robert Haas (#46)

On 3 March 2014 15:19, Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Feb 27, 2014 at 3:12 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

What I'm
really concerned about is whether there are other things like the
SnapshotNow issues that can cause stuff to halt and catch fire. I
don't know whether there are or are not, but that's my concern.

Of course its a concern, I feel it also. But that's why we have beta
period to handle the unknowns.

The question is are there any specific areas of concern here? If not,
then we commit because we've done a lot of work on it and at the
moment the balance is high benefit to users against a non-specific
feeling of risk.

@Noah - Last call...

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#48Tom Lane
tgl@sss.pgh.pa.us
In reply to: Simon Riggs (#47)

Simon Riggs <simon@2ndQuadrant.com> writes:

On 3 March 2014 15:19, Robert Haas <robertmhaas@gmail.com> wrote:

What I'm
really concerned about is whether there are other things like the
SnapshotNow issues that can cause stuff to halt and catch fire. I
don't know whether there are or are not, but that's my concern.

Of course its a concern, I feel it also. But that's why we have beta
period to handle the unknowns.

I have exactly zero faith that beta testing would catch low-probability
problems in this area. What's needed, and hasn't happened AFAIK, is
detailed study of the patch by assorted senior hackers.

The question is are there any specific areas of concern here? If not,
then we commit because we've done a lot of work on it and at the
moment the balance is high benefit to users against a non-specific
feeling of risk.

This is backwards. The default decision around here has never been
to commit when in doubt.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#49Robert Haas
robertmhaas@gmail.com
In reply to: Simon Riggs (#45)

On Sun, Mar 2, 2014 at 4:50 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

v20 includes slightly re-ordered checks in GetLockLevel, plus more
detailed comments on each group of subcommands.

Also corrects grammar as noted by Vik.

Plus adds an example of usage to the docs.

This patch contains a one line change to
src/bin/pg_dump/pg_backup_archiver.c which seems not to belong.

This hunk in ATRewriteCatalogs() looks scary:

+       /*
+        * If we think we might need to add/re-add toast tables then
+        * we currently need to hold an AccessExclusiveLock.
+        */
+       if (lockmode < AccessExclusiveLock)
+               return;

It would make sense to me to add an Assert() or elog() check inside
the subsequent loop to verify that the lock level is adequate ... but
just returning silently seems like a bad idea.

I have my doubts about whether it's safe to do AT_AddInherit,
AT_DropInherit, AT_AddOf, or AT_DropOf with a full lock. All of those
can change the tuple descriptor, and we discussed, back when we did
this the first time, the fact that the executor may get *very* unhappy
if the tuple descriptor changes in mid-execution. I strongly suspect
these are unsafe with less than a full AccessExclusiveLock.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#50Simon Riggs
simon@2ndQuadrant.com
In reply to: Tom Lane (#48)

On 3 March 2014 15:53, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Simon Riggs <simon@2ndQuadrant.com> writes:

On 3 March 2014 15:19, Robert Haas <robertmhaas@gmail.com> wrote:

What I'm
really concerned about is whether there are other things like the
SnapshotNow issues that can cause stuff to halt and catch fire. I
don't know whether there are or are not, but that's my concern.

Of course its a concern, I feel it also. But that's why we have beta
period to handle the unknowns.

I have exactly zero faith that beta testing would catch low-probability
problems in this area. What's needed, and hasn't happened AFAIK, is
detailed study of the patch by assorted senior hackers.

The question is are there any specific areas of concern here? If not,
then we commit because we've done a lot of work on it and at the
moment the balance is high benefit to users against a non-specific
feeling of risk.

This is backwards. The default decision around here has never been
to commit when in doubt.

I'm not in doubt.

If anybody can give me some more pointers of things to look at, I will.

But I've looked and I can't see anything more.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#51Simon Riggs
simon@2ndQuadrant.com
In reply to: Robert Haas (#49)

On 3 March 2014 16:06, Robert Haas <robertmhaas@gmail.com> wrote:

On Sun, Mar 2, 2014 at 4:50 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

v20 includes slightly re-ordered checks in GetLockLevel, plus more
detailed comments on each group of subcommands.

Also corrects grammar as noted by Vik.

Plus adds an example of usage to the docs.

This patch contains a one line change to
src/bin/pg_dump/pg_backup_archiver.c which seems not to belong.

This hunk in ATRewriteCatalogs() looks scary:

+       /*
+        * If we think we might need to add/re-add toast tables then
+        * we currently need to hold an AccessExclusiveLock.
+        */
+       if (lockmode < AccessExclusiveLock)
+               return;

It would make sense to me to add an Assert() or elog() check inside
the subsequent loop to verify that the lock level is adequate ... but
just returning silently seems like a bad idea.

OK, I will check elog.

I have my doubts about whether it's safe to do AT_AddInherit,
AT_DropInherit, AT_AddOf, or AT_DropOf with a full lock. All of those
can change the tuple descriptor, and we discussed, back when we did
this the first time, the fact that the executor may get *very* unhappy
if the tuple descriptor changes in mid-execution. I strongly suspect
these are unsafe with less than a full AccessExclusiveLock.

I'm happy to change those if you feel there is insufficient evidence.

I don't personally feel that it would matter to usability to keep
locks for those at AccessExclusiveLock, especially since they are
otherwise fast.

Some others might be kept higher also. I'm merely trying to balance
between requests to reduce to minimal theoretical level and fears that
anything less than AccessExclusiveLock is a problem.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#52Robert Haas
robertmhaas@gmail.com
In reply to: Simon Riggs (#51)

On Mon, Mar 3, 2014 at 11:29 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

On 3 March 2014 16:06, Robert Haas <robertmhaas@gmail.com> wrote:

On Sun, Mar 2, 2014 at 4:50 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

v20 includes slightly re-ordered checks in GetLockLevel, plus more
detailed comments on each group of subcommands.

Also corrects grammar as noted by Vik.

Plus adds an example of usage to the docs.

This patch contains a one line change to
src/bin/pg_dump/pg_backup_archiver.c which seems not to belong.

This hunk in ATRewriteCatalogs() looks scary:

+       /*
+        * If we think we might need to add/re-add toast tables then
+        * we currently need to hold an AccessExclusiveLock.
+        */
+       if (lockmode < AccessExclusiveLock)
+               return;

It would make sense to me to add an Assert() or elog() check inside
the subsequent loop to verify that the lock level is adequate ... but
just returning silently seems like a bad idea.

OK, I will check elog.

I have my doubts about whether it's safe to do AT_AddInherit,
AT_DropInherit, AT_AddOf, or AT_DropOf with a full lock. All of those
can change the tuple descriptor, and we discussed, back when we did
this the first time, the fact that the executor may get *very* unhappy
if the tuple descriptor changes in mid-execution. I strongly suspect
these are unsafe with less than a full AccessExclusiveLock.

I'm happy to change those if you feel there is insufficient evidence.

Not sure what that means, but yes, I think the lock levels on those
need to be increased.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#53Noah Misch
noah@leadboat.com
In reply to: Robert Haas (#46)

On Mon, Mar 03, 2014 at 10:19:55AM -0500, Robert Haas wrote:

On Thu, Feb 27, 2014 at 3:12 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

Removing SELECT privilege while running a SELECT would be a different
matter. This is all a matter of definition; we can make up any rules
we like. Doing so is IMHO a separate patch and not something to hold
up the main patch.

So I think this is an interesting point. There are various things
that could go wrong as a result of using the wrong lock level. Worst
would be that the server crashes or corrupts data. A little less bad
would be that sessions error out with inexplicable error conditions,
as in SnapshotNow days. Alternatively, we could just have arguably
wrong behavior, like basing query results on the old version of the
table's metadata even after it's been changed.

I would order the concerns like this:

1. Data corruption
2. Transient, clearly-wrong answers without an error
3. Server crash
4. Catalog logical inconsistency
5. Inexplicable, transient errors
6. Valid behavior capable of surprising more than zero upgraders

I don't really care about that second category of behavior. If
somebody changes some property of a table and existing sessions
continue to use the old value until eoxact, well, we can argue about
that, but at least until we have concrete reports of really
undesirable behavior, I don't think it's the primary issue. What I'm
really concerned about is whether there are other things like the
SnapshotNow issues that can cause stuff to halt and catch fire. I
don't know whether there are or are not, but that's my concern.

Since we can't know whether something qualifies as (2) or (6) without
analyzing it, I don't find waiting for user complaints to be a good strategy
here. An ownership change not immediately affecting ACL checks does fall
under (6), for me. (However, changing ownership without AccessExclusiveLock
might also create hazards in category (4) for concurrent DDL that performs
owner checks.)

--
Noah Misch
EnterpriseDB http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#54Noah Misch
noah@leadboat.com
In reply to: Simon Riggs (#47)

On Mon, Mar 03, 2014 at 03:43:46PM +0000, Simon Riggs wrote:

The question is are there any specific areas of concern here? If not,
then we commit because we've done a lot of work on it and at the
moment the balance is high benefit to users against a non-specific
feeling of risk.

@Noah - Last call...

I am not specifically aware of any outstanding problems. I have planned to
give this a close look, but it will be at least two weeks before I dig out far
enough to do so. If that makes it a post-commit review, so be it.

--
Noah Misch
EnterpriseDB http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#55Simon Riggs
simon@2ndQuadrant.com
In reply to: Noah Misch (#53)

On 3 March 2014 18:57, Noah Misch <noah@leadboat.com> wrote:

On Mon, Mar 03, 2014 at 10:19:55AM -0500, Robert Haas wrote:

On Thu, Feb 27, 2014 at 3:12 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

Removing SELECT privilege while running a SELECT would be a different
matter. This is all a matter of definition; we can make up any rules
we like. Doing so is IMHO a separate patch and not something to hold
up the main patch.

So I think this is an interesting point. There are various things
that could go wrong as a result of using the wrong lock level. Worst
would be that the server crashes or corrupts data. A little less bad
would be that sessions error out with inexplicable error conditions,
as in SnapshotNow days. Alternatively, we could just have arguably
wrong behavior, like basing query results on the old version of the
table's metadata even after it's been changed.

I would order the concerns like this:

1. Data corruption
2. Transient, clearly-wrong answers without an error
3. Server crash
4. Catalog logical inconsistency
5. Inexplicable, transient errors
6. Valid behavior capable of surprising more than zero upgraders

I like your model for risk assessment. How can we apply it in detail
in a way that helps us decide? Or do we just go on gut feel?

My experience with mentioning such topics is that without structure it
results in an assessment of "unacceptable risk" just simply because
somebody has mentioned some scary words.

I don't really care about that second category of behavior. If
somebody changes some property of a table and existing sessions
continue to use the old value until eoxact, well, we can argue about
that, but at least until we have concrete reports of really
undesirable behavior, I don't think it's the primary issue. What I'm
really concerned about is whether there are other things like the
SnapshotNow issues that can cause stuff to halt and catch fire. I
don't know whether there are or are not, but that's my concern.

Since we can't know whether something qualifies as (2) or (6) without
analyzing it, I don't find waiting for user complaints to be a good strategy
here. An ownership change not immediately affecting ACL checks does fall
under (6), for me. (However, changing ownership without AccessExclusiveLock
might also create hazards in category (4) for concurrent DDL that performs
owner checks.)

err, guys, you do realise that changing ownership is staying at
AccessExclusiveLock in this patch?
(and I haven't ever suggested lowering that).

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#56Simon Riggs
simon@2ndQuadrant.com
In reply to: Robert Haas (#52)
1 attachment(s)

On 3 March 2014 16:36, Robert Haas <robertmhaas@gmail.com> wrote:

This hunk in ATRewriteCatalogs() looks scary:

+       /*
+        * If we think we might need to add/re-add toast tables then
+        * we currently need to hold an AccessExclusiveLock.
+        */
+       if (lockmode < AccessExclusiveLock)
+               return;

It would make sense to me to add an Assert() or elog() check inside
the subsequent loop to verify that the lock level is adequate ... but
just returning silently seems like a bad idea.

OK, I will check elog.

I have my doubts about whether it's safe to do AT_AddInherit,
AT_DropInherit, AT_AddOf, or AT_DropOf with a full lock. All of those
can change the tuple descriptor, and we discussed, back when we did
this the first time, the fact that the executor may get *very* unhappy
if the tuple descriptor changes in mid-execution. I strongly suspect
these are unsafe with less than a full AccessExclusiveLock.

I'm happy to change those if you feel there is insufficient evidence.

Not sure what that means, but yes, I think the lock levels on those
need to be increased.

v21 with all requested changes, comments and cleanup

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

reduce_lock_levels.v21.patchapplication/octet-stream; name=reduce_lock_levels.v21.patchDownload
diff --git a/doc/src/sgml/mvcc.sgml b/doc/src/sgml/mvcc.sgml
index 2ca423c..ccff611 100644
--- a/doc/src/sgml/mvcc.sgml
+++ b/doc/src/sgml/mvcc.sgml
@@ -865,7 +865,7 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         <para>
          Acquired by <command>VACUUM</command> (without <option>FULL</option>),
          <command>ANALYZE</>, <command>CREATE INDEX CONCURRENTLY</>, and
-         some forms of <command>ALTER TABLE</command>.
+         <command>ALTER TABLE VALIDATE</command>.
         </para>
        </listitem>
       </varlistentry>
@@ -906,8 +906,8 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         </para>
 
         <para>
-         This lock mode is not automatically acquired by any
-         <productname>PostgreSQL</productname> command.
+         Acquired by <command>ALTER TABLE</> for subcommand types that
+         affect write operations and by <command>CREATE TRIGGER</>.
         </para>
        </listitem>
       </varlistentry>
@@ -951,7 +951,7 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         </para>
 
         <para>
-         Acquired by the <command>ALTER TABLE</>, <command>DROP TABLE</>,
+         Acquired by the <command>ALTER TABLE</> for rewriting, <command>DROP TABLE</>,
          <command>TRUNCATE</command>, <command>REINDEX</command>,
          <command>CLUSTER</command>, and <command>VACUUM FULL</command>
          commands.
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 2b02e66..5fd118f 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -84,7 +84,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
 
   <para>
    <command>ALTER TABLE</command> changes the definition of an existing table.
-   There are several subforms:
+   There are several subforms described below. Note that the lock level required
+   differs for each subform. An <literal>ACCESS EXCLUSIVE</literal> lock is held
+   unless explicitly noted. When multiple subcommands are listed, the lock
+   held will be the strictest one required from any subcommand.
 
   <variablelist>
    <varlistentry>
@@ -153,6 +156,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       or <command>UPDATE</> commands; they do not cause rows already in the
       table to change.
      </para>
+     <para>
+      This form requires only a <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -181,6 +187,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <productname>PostgreSQL</productname> query planner, refer to
       <xref linkend="planner-stats">.
      </para>
+     <para>
+      This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -213,6 +222,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       of statistics by the <productname>PostgreSQL</productname> query
       planner, refer to <xref linkend="planner-stats">.
      </para>
+     <para>
+      This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -264,6 +276,12 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       the table, until it is validated by using the <literal>VALIDATE
       CONSTRAINT</literal> option.
      </para>
+     <para>
+      This form requires only a <literal>SHARE ROW EXCLUSIVE</literal> lock
+      on the table being altered. If the constraint is a foreign key then
+      a <literal>SHARE UPDATE EXCLUSIVE</literal> lock is also required on
+      the table referenced by the constraint.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -325,6 +343,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       This form alters the attributes of a constraint that was previously
       created. Currently only foreign key constraints may be altered.
      </para>
+     <para>
+      This form requires only a <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -338,11 +359,17 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       Nothing happens if the constraint is already marked valid.
      </para>
      <para>
-      Validation can be a long process on larger tables and currently requires
-      an <literal>ACCESS EXCLUSIVE</literal> lock.  The value of separating
+      Validation can be a long process on larger tables. The value of separating
       validation from initial creation is that you can defer validation to less
       busy times, or can be used to give additional time to correct pre-existing
-      errors while preventing new errors.
+      errors while preventing new errors. Note also that validation on its own
+      does not prevent normal write commands against the table while it runs.
+     </para>
+     <para>
+      This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock
+      on the table being altered. If the constraint is a foreign key then
+      a <literal>ROW SHARE</literal> lock is also required on
+      the table referenced by the constraint.
      </para>
     </listitem>
    </varlistentry>
@@ -383,6 +410,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       mode, and triggers configured as <literal>ENABLE ALWAYS</literal> will
       fire regardless of the current replication mode.
      </para>
+     <para>
+      This form requires only a <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -408,6 +438,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <xref linkend="SQL-CLUSTER">
       operations.  It does not actually re-cluster the table.
      </para>
+     <para>
+      This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -420,6 +453,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       index specification from the table.  This affects
       future cluster operations that don't specify an index.
      </para>
+     <para>
+      This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -467,6 +503,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       FULL</>, <xref linkend="SQL-CLUSTER"> or one of the forms
       of <command>ALTER TABLE</> that forces a table rewrite.
      </para>
+     <para>
+      This form requires only a <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
 
      <note>
       <para>
@@ -489,6 +528,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       defaults.  As with <literal>SET</>, a table rewrite might be
       needed to update the table entirely.
      </para>
+     <para>
+      This form requires only a <literal>SHARE ROW EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -517,6 +559,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <literal>FOREIGN KEY</literal> constraints are not considered, but
       this might change in the future.
      </para>
+     <para>
+      This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -529,6 +574,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       Queries against the parent table will no longer include records drawn
       from the target table.
      </para>
+     <para>
+      This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -544,6 +592,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       that <command>CREATE TABLE OF</> would permit an equivalent table
       definition.
      </para>
+     <para>
+      This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -553,6 +604,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
      <para>
       This form dissociates a typed table from its type.
      </para>
+     <para>
+      This form requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -1075,6 +1129,14 @@ ALTER TABLE distributors ADD CONSTRAINT distfk FOREIGN KEY (address) REFERENCES
   </para>
 
   <para>
+   To add a foreign key constraint to a table minimising impact on other work:
+<programlisting>
+ALTER TABLE distributors ADD CONSTRAINT distfk FOREIGN KEY (address) REFERENCES addresses (address) NOT VALID;
+ALTER TABLE distributors VALIDATE CONSTRAINT distfk;
+</programlisting>
+  </para>
+
+  <para>
    To add a (multicolumn) unique constraint to a table:
 <programlisting>
 ALTER TABLE distributors ADD CONSTRAINT dist_id_zipcode_key UNIQUE (dist_id, zipcode);
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index f044280..90f1794 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -27,6 +27,7 @@
 #include "catalog/toasting.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "storage/lock.h"
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
@@ -35,7 +36,7 @@
 Oid			binary_upgrade_next_toast_pg_type_oid = InvalidOid;
 
 static bool create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
-				   Datum reloptions);
+				   Datum reloptions, LOCKMODE lockmode);
 static bool needs_toast_table(Relation rel);
 
 
@@ -52,7 +53,7 @@ static bool needs_toast_table(Relation rel);
  * to end with CommandCounterIncrement if it makes any changes.
  */
 void
-AlterTableCreateToastTable(Oid relOid, Datum reloptions)
+AlterTableCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode)
 {
 	Relation	rel;
 
@@ -63,10 +64,10 @@ AlterTableCreateToastTable(Oid relOid, Datum reloptions)
 	 * concurrent readers of the pg_class tuple won't have visibility issues,
 	 * so let's be safe.
 	 */
-	rel = heap_open(relOid, AccessExclusiveLock);
+	rel = heap_open(relOid, lockmode);
 
 	/* create_toast_table does all the work */
-	(void) create_toast_table(rel, InvalidOid, InvalidOid, reloptions);
+	(void) create_toast_table(rel, InvalidOid, InvalidOid, reloptions, lockmode);
 
 	heap_close(rel, NoLock);
 }
@@ -91,7 +92,8 @@ BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid)
 						relName)));
 
 	/* create_toast_table does all the work */
-	if (!create_toast_table(rel, toastOid, toastIndexOid, (Datum) 0))
+	if (!create_toast_table(rel, toastOid, toastIndexOid, (Datum) 0,
+							AccessExclusiveLock))
 		elog(ERROR, "\"%s\" does not require a toast table",
 			 relName);
 
@@ -107,7 +109,8 @@ BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid)
  * bootstrap they can be nonzero to specify hand-assigned OIDs
  */
 static bool
-create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, Datum reloptions)
+create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
+				   Datum reloptions, LOCKMODE lockmode)
 {
 	Oid			relOid = RelationGetRelid(rel);
 	HeapTuple	reltup;
@@ -161,6 +164,14 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, Datum reloptio
 		return false;
 
 	/*
+	 * We need to create a toast table. We shouldn't
+	 * have got this far if we're in ALTER TABLE unless
+	 * we are holding AccessExclusiveLock.
+	 */
+	if (lockmode < AccessExclusiveLock)
+		elog(ERROR, "AccessExclusiveLock required to add toast tables.");
+
+	/*
 	 * Create the toast table and its index
 	 */
 	snprintf(toast_relname, sizeof(toast_relname),
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 8b18e4a..bbe87ae 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -714,7 +714,11 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, bool forcetemp,
 		if (isNull)
 			reloptions = (Datum) 0;
 
-		AlterTableCreateToastTable(OIDNewHeap, reloptions);
+		/*
+		 * Adding a toast table means we must have an AccessExclusiveLock,
+		 * which we need to have acquired before we examine the table.
+		 */
+		AlterTableCreateToastTable(OIDNewHeap, reloptions, AccessExclusiveLock);
 
 		ReleaseSysCache(tuple);
 	}
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 73e6e20..ed38295 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -373,7 +373,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 
 	(void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true);
 
-	AlterTableCreateToastTable(intoRelationId, toast_options);
+	AlterTableCreateToastTable(intoRelationId, toast_options, AccessExclusiveLock);
 
 	/* Create the "view" part of a materialized view. */
 	if (is_matview)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1de3170..e4ff88f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2687,8 +2687,7 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
  * The caller must lock the relation, with an appropriate lock level
  * for the subcommands requested. Any subcommand that needs to rewrite
  * tuples in the table forces the whole command to be executed with
- * AccessExclusiveLock (actually, that is currently required always, but
- * we hope to relax it at some point).	We pass the lock level down
+ * AccessExclusiveLock.  We pass the lock level down
  * so that we can apply it recursively to inherited tables. Note that the
  * lock level we want as we recurse might well be higher than required for
  * that specific subcommand. So we pass down the overall lock requirement,
@@ -2755,30 +2754,8 @@ LOCKMODE
 AlterTableGetLockLevel(List *cmds)
 {
 	/*
-	 * Late in 9.1 dev cycle a number of issues were uncovered with access to
-	 * catalog relations, leading to the decision to re-enforce all DDL at
-	 * AccessExclusiveLock level by default.
-	 *
-	 * The issues are that there is a pervasive assumption in the code that
-	 * the catalogs will not be read unless an AccessExclusiveLock is held. If
-	 * that rule is relaxed, we must protect against a number of potential
-	 * effects - infrequent, but proven possible with test cases where
-	 * multiple DDL operations occur in a stream against frequently accessed
-	 * tables.
-	 *
-	 * 1. Catalog tables were read using SnapshotNow, which has a race bug that
-	 * allows a scan to return no valid rows even when one is present in the
-	 * case of a commit of a concurrent update of the catalog table.
-	 * SnapshotNow also ignores transactions in progress, so takes the latest
-	 * committed version without waiting for the latest changes.
-	 *
-	 * 2. Relcache needs to be internally consistent, so unless we lock the
-	 * definition during reads we have no way to guarantee that.
-	 *
-	 * 3. Catcache access isn't coordinated at all so refreshes can occur at
-	 * any time.
+	 * This only works if we read catalog tables using MVCC snapshots.
 	 */
-#ifdef REDUCED_ALTER_TABLE_LOCK_LEVELS
 	ListCell   *lcmd;
 	LOCKMODE	lockmode = ShareUpdateExclusiveLock;
 
@@ -2790,28 +2767,55 @@ AlterTableGetLockLevel(List *cmds)
 		switch (cmd->subtype)
 		{
 				/*
-				 * Need AccessExclusiveLock for these subcommands because they
-				 * affect or potentially affect both read and write
-				 * operations.
-				 *
-				 * New subcommand types should be added here by default.
+				 * These subcommands rewrite the heap, so require full locks.
 				 */
 			case AT_AddColumn:	/* may rewrite heap, in some cases and visible
 								 * to SELECT */
-			case AT_DropColumn:	/* change visible to SELECT */
-			case AT_AddColumnToView:	/* CREATE VIEW */
+			case AT_SetTableSpace:		/* must rewrite heap */
 			case AT_AlterColumnType:	/* must rewrite heap */
+			case AT_AddOids:			/* must rewrite heap */
+				cmd_lockmode = AccessExclusiveLock;
+				break;
+
+				/*
+				 * These subcommands may require addition of toast tables.
+				 */
+			case AT_SetStorage:			/* may add toast tables, see ATRewriteCatalogs() */
+				cmd_lockmode = AccessExclusiveLock;
+				break;
+
+				/*
+				 * Removing constraints can affect SELECTs that have been
+				 * optimised assuming the constraint holds true.
+				 */
 			case AT_DropConstraint:		/* as DROP INDEX */
-			case AT_AddOids:	/* must rewrite heap */
-			case AT_DropOids:	/* calls AT_DropColumn */
+			case AT_DropNotNull:		/* may change some SQL plans */
+				cmd_lockmode = AccessExclusiveLock;
+				break;
+
+				/*
+				 * Subcommands that may be visible to concurrent SELECTs
+				 */
+			case AT_DropColumn:			/* change visible to SELECT */
+			case AT_AddColumnToView:	/* CREATE VIEW */
+			case AT_DropOids:			/* calls AT_DropColumn */
 			case AT_EnableAlwaysRule:	/* may change SELECT rules */
 			case AT_EnableReplicaRule:	/* may change SELECT rules */
-			case AT_EnableRule:	/* may change SELECT rules */
+			case AT_EnableRule:			/* may change SELECT rules */
 			case AT_DisableRule:		/* may change SELECT rules */
+				cmd_lockmode = AccessExclusiveLock;
+				break;
+
+				/*
+				 * Changing owner may remove implicit SELECT privileges
+				 */
 			case AT_ChangeOwner:		/* change visible to SELECT */
-			case AT_SetTableSpace:		/* must rewrite heap */
-			case AT_DropNotNull:		/* may change some SQL plans */
-			case AT_SetNotNull:
+				cmd_lockmode = AccessExclusiveLock;
+				break;
+
+				/*
+				 * Changing foreign table options may affect optimisation.
+				 */
 			case AT_GenericOptions:
 			case AT_AlterColumnGenericOptions:
 				cmd_lockmode = AccessExclusiveLock;
@@ -2832,9 +2836,11 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_DisableTrig:
 			case AT_DisableTrigAll:
 			case AT_DisableTrigUser:
+			case AT_AlterConstraint:
 			case AT_AddIndex:	/* from ADD CONSTRAINT */
 			case AT_AddIndexConstraint:
 			case AT_ReplicaIdentity:
+			case AT_SetNotNull:
 				cmd_lockmode = ShareRowExclusiveLock;
 				break;
 
@@ -2879,22 +2885,23 @@ AlterTableGetLockLevel(List *cmds)
 				 * behaviour, while queries started after we commit will see
 				 * new behaviour. No need to prevent reads or writes to the
 				 * subtable while we hook it up though.
+				 * Changing the TupDesc may be a problem, so keep highest lock.
 				 */
 			case AT_AddInherit:
 			case AT_DropInherit:
-				cmd_lockmode = ShareUpdateExclusiveLock;
+				cmd_lockmode = AccessExclusiveLock;
 				break;
 
 				/*
 				 * These subcommands affect implicit row type conversion. They
-				 * have affects similar to CREATE/DROP CAST on queries.  We
+				 * have affects similar to CREATE/DROP CAST on queries.
 				 * don't provide for invalidating parse trees as a result of
-				 * such changes.  Do avoid concurrent pg_class updates,
-				 * though.
+				 * such changes, so we keep these at AccessExclusiveLock.
 				 */
 			case AT_AddOf:
 			case AT_DropOf:
-				cmd_lockmode = ShareUpdateExclusiveLock;
+				cmd_lockmode = AccessExclusiveLock;
+				break;
 
 				/*
 				 * These subcommands affect general strategies for performance
@@ -2914,8 +2921,6 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_ReplaceRelOptions:
 			case AT_SetOptions:
 			case AT_ResetOptions:
-			case AT_SetStorage:
-			case AT_AlterConstraint:
 			case AT_ValidateConstraint:
 				cmd_lockmode = ShareUpdateExclusiveLock;
 				break;
@@ -2932,9 +2937,6 @@ AlterTableGetLockLevel(List *cmds)
 		if (cmd_lockmode > lockmode)
 			lockmode = cmd_lockmode;
 	}
-#else
-	LOCKMODE	lockmode = AccessExclusiveLock;
-#endif
 
 	return lockmode;
 }
@@ -3269,7 +3271,7 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
 
 		if (tab->relkind == RELKIND_RELATION ||
 			tab->relkind == RELKIND_MATVIEW)
-			AlterTableCreateToastTable(tab->relid, (Datum) 0);
+			AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
 	}
 }
 
@@ -5870,9 +5872,9 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * deadlock.)
 	 */
 	if (OidIsValid(fkconstraint->old_pktable_oid))
-		pkrel = heap_open(fkconstraint->old_pktable_oid, AccessExclusiveLock);
+		pkrel = heap_open(fkconstraint->old_pktable_oid, ShareUpdateExclusiveLock);
 	else
-		pkrel = heap_openrv(fkconstraint->pktable, AccessExclusiveLock);
+		pkrel = heap_openrv(fkconstraint->pktable, ShareUpdateExclusiveLock);
 
 	/*
 	 * Validity checks (permission checks wait till we have the column
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index fa74bd2..a0ea474 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -156,9 +156,9 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 				referenced;
 
 	if (OidIsValid(relOid))
-		rel = heap_open(relOid, AccessExclusiveLock);
+		rel = heap_open(relOid, ShareUpdateExclusiveLock);
 	else
-		rel = heap_openrv(stmt->relation, AccessExclusiveLock);
+		rel = heap_openrv(stmt->relation, ShareUpdateExclusiveLock);
 
 	/*
 	 * Triggers must be on tables or views, and there are additional
@@ -500,8 +500,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * can skip this for internally generated triggers, since the name
 	 * modification above should be sufficient.
 	 *
-	 * NOTE that this is cool only because we have AccessExclusiveLock on the
-	 * relation, so the trigger set won't be changing underneath us.
+	 * NOTE that this is cool only because we have a sufficient lock on the
+	 * relation to ensure the trigger set won't be changing underneath us.
 	 */
 	if (!isInternal)
 	{
@@ -1077,7 +1077,7 @@ RemoveTriggerById(Oid trigOid)
 	 */
 	relid = ((Form_pg_trigger) GETSTRUCT(tup))->tgrelid;
 
-	rel = heap_open(relid, AccessExclusiveLock);
+	rel = heap_open(relid, ShareUpdateExclusiveLock);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
 		rel->rd_rel->relkind != RELKIND_VIEW)
@@ -1243,8 +1243,8 @@ renametrig(RenameStmt *stmt)
 	 * on tgrelid/tgname would complain anyway) and to ensure a trigger does
 	 * exist with oldname.
 	 *
-	 * NOTE that this is cool only because we have AccessExclusiveLock on the
-	 * relation, so the trigger set won't be changing underneath us.
+	 * NOTE that this is cool only because we have a sufficient lock on the
+	 * relation to ensure that the trigger set won't be changing underneath us.
 	 */
 	tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index d1621ad..b15f5dc 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -927,7 +927,8 @@ ProcessUtilitySlow(Node *parsetree,
 												   toast_options,
 												   true);
 
-							AlterTableCreateToastTable(relOid, toast_options);
+							AlterTableCreateToastTable(relOid, toast_options,
+													   AccessExclusiveLock);
 						}
 						else if (IsA(stmt, CreateForeignTableStmt))
 						{
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2810b35..086d9f8 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -161,6 +161,14 @@ static bool eoxact_list_overflowed = false;
 			eoxact_list_overflowed = true; \
 	} while (0)
 
+/*
+ * EOXactTupleDescArray stores *TupleDescs that (might) need AtEOXact
+ * cleanup work.  The array expands as needed; there is no hashtable because
+ * we don't need to access individual items except at EOXact.
+ */
+static TupleDesc *EOXactTupleDescArray;
+static int NextEOXactTupleDescNum = 0;
+static int EOXactTupleDescArrayLen = 0;
 
 /*
  *		macros to manipulate the lookup hashtables
@@ -219,11 +227,12 @@ static HTAB *OpClassCache = NULL;
 
 /* non-export function prototypes */
 
-static void RelationDestroyRelation(Relation relation);
+static void RelationDestroyRelation(Relation relation, bool remember_tupdesc);
 static void RelationClearRelation(Relation relation, bool rebuild);
 
 static void RelationReloadIndexInfo(Relation relation);
 static void RelationFlushRelation(Relation relation);
+static void RememberToFreeTupleDescAtEOX(TupleDesc td);
 static void AtEOXact_cleanup(Relation relation, bool isCommit);
 static void AtEOSubXact_cleanup(Relation relation, bool isCommit,
 					SubTransactionId mySubid, SubTransactionId parentSubid);
@@ -1811,7 +1820,7 @@ RelationReloadIndexInfo(Relation relation)
  *	Caller must already have unhooked the entry from the hash table.
  */
 static void
-RelationDestroyRelation(Relation relation)
+RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 {
 	Assert(RelationHasReferenceCountZero(relation));
 
@@ -1831,7 +1840,20 @@ RelationDestroyRelation(Relation relation)
 	/* can't use DecrTupleDescRefCount here */
 	Assert(relation->rd_att->tdrefcount > 0);
 	if (--relation->rd_att->tdrefcount == 0)
-		FreeTupleDesc(relation->rd_att);
+	{
+		/*
+		 * If we Rebuilt a relcache entry during a transaction then its
+		 * possible we did that because the TupDesc changed as the result
+		 * of an ALTER TABLE that ran at less than AccessExclusiveLock.
+		 * It's possible someone copied that TupDesc, in which case the
+		 * copy would point to free'd memory. So if we rebuild an entry
+		 * we keep the TupDesc around until end of transaction, to be safe.
+		 */
+		if (remember_tupdesc)
+			RememberToFreeTupleDescAtEOX(relation->rd_att);
+		else
+			FreeTupleDesc(relation->rd_att);
+	}
 	list_free(relation->rd_indexlist);
 	bms_free(relation->rd_indexattr);
 	FreeTriggerDesc(relation->trigdesc);
@@ -1945,7 +1967,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 		RelationCacheDelete(relation);
 
 		/* And release storage */
-		RelationDestroyRelation(relation);
+		RelationDestroyRelation(relation, false);
 	}
 	else if (!IsTransactionState())
 	{
@@ -2012,7 +2034,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 		{
 			/* Should only get here if relation was deleted */
 			RelationCacheDelete(relation);
-			RelationDestroyRelation(relation);
+			RelationDestroyRelation(relation, false);
 			elog(ERROR, "relation %u deleted while still in use", save_relid);
 		}
 
@@ -2074,7 +2096,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 #undef SWAPFIELD
 
 		/* And now we can throw away the temporary entry */
-		RelationDestroyRelation(newrel);
+		RelationDestroyRelation(newrel, !keep_tupdesc);
 	}
 }
 
@@ -2312,6 +2334,37 @@ RelationCloseSmgrByOid(Oid relationId)
 	RelationCloseSmgr(relation);
 }
 
+void
+RememberToFreeTupleDescAtEOX(TupleDesc td)
+{
+	if (EOXactTupleDescArray == NULL)
+	{
+		MemoryContext	oldcxt;
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+		EOXactTupleDescArray = (TupleDesc *) palloc(16 * sizeof(TupleDesc));
+		EOXactTupleDescArrayLen = 16;
+		NextEOXactTupleDescNum = 0;
+		MemoryContextSwitchTo(oldcxt);
+	}
+	else if (NextEOXactTupleDescNum >= EOXactTupleDescArrayLen)
+	{
+		MemoryContext	oldcxt;
+		int32       	newlen = EOXactTupleDescArrayLen * 2;
+
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+		Assert(EOXactTupleDescArrayLen > 0);
+
+		EOXactTupleDescArray = (TupleDesc *) repalloc(EOXactTupleDescArray,
+														newlen * sizeof(TupleDesc));
+		EOXactTupleDescArrayLen = newlen;
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	EOXactTupleDescArray[NextEOXactTupleDescNum++] = td;
+}
+
 /*
  * AtEOXact_RelationCache
  *
@@ -2367,9 +2420,20 @@ AtEOXact_RelationCache(bool isCommit)
 		}
 	}
 
-	/* Now we're out of the transaction and can clear the list */
+	if (EOXactTupleDescArrayLen > 0)
+	{
+		Assert(EOXactTupleDescArray != NULL);
+		for (i = 0; i < NextEOXactTupleDescNum; i++)
+			FreeTupleDesc(EOXactTupleDescArray[i]);
+		pfree(EOXactTupleDescArray);
+		EOXactTupleDescArray = NULL;
+	}
+
+	/* Now we're out of the transaction and can clear the lists */
 	eoxact_list_len = 0;
 	eoxact_list_overflowed = false;
+	NextEOXactTupleDescNum = 0;
+	EOXactTupleDescArrayLen = 0;
 }
 
 /*
diff --git a/src/include/catalog/toasting.h b/src/include/catalog/toasting.h
index ca047f0..07b2169 100644
--- a/src/include/catalog/toasting.h
+++ b/src/include/catalog/toasting.h
@@ -14,10 +14,13 @@
 #ifndef TOASTING_H
 #define TOASTING_H
 
+#include "storage/lock.h"
+
 /*
  * toasting.c prototypes
  */
-extern void AlterTableCreateToastTable(Oid relOid, Datum reloptions);
+extern void AlterTableCreateToastTable(Oid relOid, Datum reloptions,
+									   LOCKMODE lockmode);
 extern void BootstrapToastTable(char *relName,
 					Oid toastOid, Oid toastIndexOid);
 
diff --git a/src/test/isolation/expected/alter-table-1.out b/src/test/isolation/expected/alter-table-1.out
new file mode 100644
index 0000000..9daa418
--- /dev/null
+++ b/src/test/isolation/expected/alter-table-1.out
@@ -0,0 +1,3676 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1 at1 sc1 s2 at2 sc2 rx1 wx rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 at2 rx1 sc2 wx rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 at2 rx1 wx sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 at2 rx1 wx rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 at2 rx1 wx rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 at2 sc2 wx rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 at2 wx sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 at2 wx rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 at2 wx rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx at2 sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx at2 rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx at2 rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx rx1 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx rx1 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx rx1 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 at2 sc2 wx rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 at2 wx sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 at2 wx rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 at2 wx rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx at2 sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx at2 rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx at2 rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx rx1 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx rx1 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx rx1 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 at2 sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 at2 rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 at2 rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 rx1 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 rx1 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 rx1 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx rx1 s2 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx rx1 s2 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx rx1 s2 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx rx1 c2 s2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 at2 sc2 wx rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 at2 wx sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 at2 wx rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 at2 wx rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx at2 sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx at2 rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx at2 rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx rx1 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx rx1 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx rx1 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 at2 sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 at2 rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 at2 rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 rx1 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 rx1 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 rx1 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx rx1 s2 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx rx1 s2 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx rx1 s2 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx rx1 c2 s2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 s2 at2 sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 s2 at2 rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 s2 at2 rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 s2 rx1 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 s2 rx1 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 s2 rx1 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 rx1 s2 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 rx1 s2 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 rx1 s2 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 rx1 c2 s2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx rx1 sc1 s2 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 at1 rx1 wx rx1 sc1 s2 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 at1 rx1 wx rx1 sc1 s2 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 at1 rx1 wx rx1 sc1 c2 s2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 at1 rx1 wx rx1 c2 sc1 s2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 sc1 s2 at2 sc2 wx rx1 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 at2 wx sc2 rx1 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 at2 wx rx1 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 at2 wx rx1 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 wx at2 sc2 rx1 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 wx at2 rx1 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 wx at2 rx1 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 wx rx1 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 wx rx1 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 s2 wx rx1 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx s2 at2 sc2 rx1 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx s2 at2 rx1 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx s2 at2 rx1 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx s2 rx1 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx s2 rx1 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx s2 rx1 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx rx1 s2 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx rx1 s2 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx rx1 s2 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 sc1 wx rx1 c2 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 s2 at2 sc2 rx1 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 s2 at2 rx1 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 s2 at2 rx1 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 s2 rx1 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 s2 rx1 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 s2 rx1 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 rx1 s2 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 rx1 s2 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 rx1 s2 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx sc1 rx1 c2 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx rx1 sc1 s2 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx rx1 sc1 s2 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx rx1 sc1 s2 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx rx1 sc1 c2 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx rx1 c2 sc1 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 s2 at2 sc2 rx1 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 s2 at2 rx1 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 s2 at2 rx1 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 s2 rx1 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 s2 rx1 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 s2 rx1 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 rx1 s2 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 rx1 s2 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 rx1 s2 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 rx1 c2 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 rx1 sc1 s2 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 rx1 sc1 s2 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 rx1 sc1 s2 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 rx1 sc1 c2 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 rx1 c2 sc1 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 wx rx1 at1 sc1 s2 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx rx1 at1 sc1 s2 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx rx1 at1 sc1 s2 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx rx1 at1 sc1 c2 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx rx1 at1 c2 sc1 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 wx rx1 c2 at1 sc1 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 at2 sc2 wx rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 at2 wx sc2 rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 at2 wx rx1 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 at2 wx rx1 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 wx at2 sc2 rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 wx at2 rx1 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 wx at2 rx1 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 wx rx1 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 wx rx1 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 wx rx1 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx s2 at2 sc2 rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx s2 at2 rx1 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx s2 at2 rx1 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx s2 rx1 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx s2 rx1 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx s2 rx1 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx rx1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx rx1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx rx1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 wx rx1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 s2 at2 sc2 rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 s2 at2 rx1 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 s2 at2 rx1 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 s2 rx1 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 s2 rx1 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 s2 rx1 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 rx1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 rx1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 rx1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx sc1 rx1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+step sc1: COMMIT;
+step wx: <... completed>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx rx1 sc1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx rx1 sc1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx rx1 sc1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx rx1 sc1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx rx1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step wx: INSERT INTO b VALUES (0); <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 s2 at2 sc2 rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 s2 at2 rx1 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 s2 at2 rx1 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 s2 rx1 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 s2 rx1 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 s2 rx1 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 rx1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 rx1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 rx1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 rx1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 rx1 sc1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 rx1 sc1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 rx1 sc1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 rx1 sc1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 rx1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 wx rx1 at1 sc1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx rx1 at1 sc1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx rx1 at1 sc1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx rx1 at1 sc1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx rx1 at1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 wx rx1 c2 at1 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx s1 at1 sc1 s2 at2 sc2 rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 s2 at2 rx1 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 s2 at2 rx1 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 s2 rx1 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 s2 rx1 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 s2 rx1 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 rx1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 rx1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 rx1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 rx1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 rx1 sc1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 rx1 sc1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 rx1 sc1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 rx1 sc1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 rx1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx s1 rx1 at1 sc1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 rx1 at1 sc1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 rx1 at1 sc1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 rx1 at1 sc1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 rx1 at1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx s1 rx1 c2 at1 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx rx1 s1 at1 sc1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx rx1 s1 at1 sc1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx rx1 s1 at1 sc1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx rx1 s1 at1 sc1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx rx1 s1 at1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx rx1 s1 c2 at1 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s1: BEGIN;
+step c2: COMMIT;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx rx1 c2 s1 at1 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
diff --git a/src/test/isolation/expected/alter-table-2.out b/src/test/isolation/expected/alter-table-2.out
new file mode 100644
index 0000000..9e957bf
--- /dev/null
+++ b/src/test/isolation/expected/alter-table-2.out
@@ -0,0 +1,1681 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1 at1 c1 rx1 wx1 rx2 wx2 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+442368         
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+811008         
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 c1 wx1 rx2 wx2 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step c1: COMMIT;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+442368         
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+811008         
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx1 c1 rx2 wx2 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx1 rx2 c1 wx2 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx1 rx2 wx2 c1 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx1 rx2 wx2 rx3 c1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx1 rx2 wx2 rx3 c2 c1
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: s1 rx1 at1 c1 wx1 rx2 wx2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+442368         
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+811008         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx1 c1 rx2 wx2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx1 rx2 c1 wx2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx1 rx2 wx2 c1 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx1 rx2 wx2 rx3 c1 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx1 rx2 wx2 rx3 c2 c1
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: s1 rx1 wx1 at1 c1 rx2 wx2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 at1 rx2 c1 wx2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 at1 rx2 wx2 c1 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 at1 rx2 wx2 rx3 c1 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 at1 rx2 wx2 rx3 c2 c1
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 at1 c1 wx2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 at1 wx2 c1 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 at1 wx2 rx3 c1 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 at1 wx2 rx3 c2 c1
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 wx2 at1 c1 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 wx2 at1 rx3 c1 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 wx2 at1 rx3 c2 c1
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 wx2 rx3 at1 c1 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 wx2 rx3 at1 c2 c1
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 wx2 rx3 c2 at1 c1
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+
+starting permutation: rx1 s1 at1 c1 wx1 rx2 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+442368         
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+811008         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx1 c1 rx2 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx1 rx2 c1 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx1 rx2 wx2 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx1 rx2 wx2 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx1 rx2 wx2 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 s1 wx1 at1 c1 rx2 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 at1 rx2 c1 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 at1 rx2 wx2 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 at1 rx2 wx2 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 at1 rx2 wx2 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 at1 c1 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 at1 wx2 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 at1 wx2 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 at1 wx2 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 wx2 at1 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 wx2 at1 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 wx2 at1 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 wx2 rx3 at1 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 wx2 rx3 at1 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 wx2 rx3 c2 at1 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 s1 at1 c1 rx2 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 at1 rx2 c1 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 at1 rx2 wx2 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 at1 rx2 wx2 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 at1 rx2 wx2 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 at1 c1 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 at1 wx2 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 at1 wx2 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 at1 wx2 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 wx2 at1 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 wx2 at1 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 wx2 at1 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 wx2 rx3 at1 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 wx2 rx3 at1 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 wx2 rx3 c2 at1 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 at1 c1 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 at1 wx2 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 at1 wx2 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 at1 wx2 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 wx2 at1 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 wx2 at1 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 wx2 at1 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 wx2 rx3 at1 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 wx2 rx3 at1 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 wx2 rx3 c2 at1 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 s1 at1 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 s1 at1 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 s1 at1 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 s1 rx3 at1 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 s1 rx3 at1 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 s1 rx3 c2 at1 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 rx3 s1 at1 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 rx3 s1 at1 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 rx3 s1 c2 at1 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step s1: BEGIN;
+step c2: COMMIT;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 rx3 c2 s1 at1 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 1e73b4a..6fd2fe1 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -23,4 +23,6 @@ test: multixact-no-deadlock
 test: multixact-no-forget
 test: propagate-lock-delete
 test: drop-index-concurrently-1
+test: alter-table-1
+test: alter-table-2
 test: timeouts
diff --git a/src/test/isolation/specs/alter-table-1.spec b/src/test/isolation/specs/alter-table-1.spec
new file mode 100644
index 0000000..f01084d
--- /dev/null
+++ b/src/test/isolation/specs/alter-table-1.spec
@@ -0,0 +1,34 @@
+# ALTER TABLE - Add and Validate constraint with concurrent writes
+#
+# ADD FOREIGN KEY allows a minimum of ShareRowExclusiveLock,
+# so we mix writes with it to see what works or waits.
+# VALIDATE allows a minimum of ShareUpdateExclusiveLock
+# so we mix reads with it to see what works or waits
+
+setup
+{
+ CREATE TABLE a (i int PRIMARY KEY);
+ CREATE TABLE b (a_id int);
+ INSERT INTO a VALUES (0), (1), (2), (3);
+ INSERT INTO b SELECT generate_series(1,1000) % 4;
+}
+
+teardown
+{
+ DROP TABLE a, b;
+}
+
+session "s1"
+step "s1"	{ BEGIN; }
+step "at1"	{ ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; }
+step "sc1"	{ COMMIT; }
+step "s2"	{ BEGIN; }
+step "at2"	{ ALTER TABLE b VALIDATE CONSTRAINT bfk; }
+step "sc2"	{ COMMIT; }
+
+session "s2"
+setup		{ BEGIN; }
+step "rx1"	{ SELECT * FROM b WHERE a_id = 1 LIMIT 1; }
+step "wx"	{ INSERT INTO b VALUES (0); }
+step "rx1"	{ SELECT * FROM b WHERE a_id = 3 LIMIT 3; }
+step "c2"	{ COMMIT; }
diff --git a/src/test/isolation/specs/alter-table-2.spec b/src/test/isolation/specs/alter-table-2.spec
new file mode 100644
index 0000000..bd0f3ba
--- /dev/null
+++ b/src/test/isolation/specs/alter-table-2.spec
@@ -0,0 +1,29 @@
+# ALTER TABLE - Change fillfactor
+#
+# As the fillfactor takes effect the table grows much larger
+
+setup
+{
+ CREATE TABLE b (a_id int);
+ ALTER TABLE b SET (fillfactor = 100);
+ INSERT INTO b SELECT generate_series(1,1000) % 4;
+}
+
+teardown
+{
+ DROP TABLE b;
+}
+
+session "s1"
+step "s1"	{ BEGIN; }
+step "at1"	{ ALTER TABLE b SET (fillfactor = 10); }
+step "c1"	{ COMMIT; }
+
+session "s2"
+setup		{ BEGIN; }
+step "rx1"	{ SELECT pg_total_relation_size('b'); }
+step "wx1"	{ INSERT INTO b SELECT generate_series(1,1000) % 4; }
+step "rx2"	{ SELECT pg_total_relation_size('b'); }
+step "wx2"	{ INSERT INTO b SELECT generate_series(1,1000) % 4; }
+step "rx3"	{ SELECT pg_total_relation_size('b'); }
+step "c2"	{ COMMIT; }
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 0f0c638..316d789 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1840,72 +1840,75 @@ and relnamespace != (select oid from pg_namespace where nspname = 'pg_catalog')
 and c.relname != 'my_locks'
 group by c.relname;
 create table alterlock (f1 int primary key, f2 text);
+insert into alterlock values (1, 'foo');
+create table alterlock2 (f3 int primary key, f1 int);
+insert into alterlock2 values (1, 1);
 begin; alter table alterlock alter column f2 set statistics 150;
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
 begin; alter table alterlock cluster on alterlock_pkey;
 select * from my_locks order by 1;
-    relname     |    max_lockmode     
-----------------+---------------------
- alterlock      | AccessExclusiveLock
- alterlock_pkey | AccessExclusiveLock
+    relname     |       max_lockmode       
+----------------+--------------------------
+ alterlock      | ShareUpdateExclusiveLock
+ alterlock_pkey | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set without cluster;
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 commit;
 begin; alter table alterlock set (fillfactor = 100);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock reset (fillfactor);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set (toast.autovacuum_enabled = off);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set (autovacuum_enabled = off);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock alter column f2 set (n_distinct = 1);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
@@ -1919,13 +1922,67 @@ select * from my_locks order by 1;
 rollback;
 begin; alter table alterlock alter column f2 set default 'x';
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |     max_lockmode      
+-----------+-----------------------
+ alterlock | ShareRowExclusiveLock
+(1 row)
+
+rollback;
+begin;
+create trigger ttdummy
+	before delete or update on alterlock
+	for each row
+	execute procedure
+	ttdummy (1, 1);
+select * from my_locks order by 1;
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
+begin;
+select * from my_locks order by 1;
+ relname | max_lockmode 
+---------+--------------
+(0 rows)
+
+alter table alterlock2 add foreign key (f1) references alterlock (f1);
+select * from my_locks order by 1;
+     relname     |       max_lockmode       
+-----------------+--------------------------
+ alterlock       | ShareUpdateExclusiveLock
+ alterlock2      | ShareRowExclusiveLock
+ alterlock2_pkey | AccessShareLock
+ alterlock_pkey  | AccessShareLock
+(4 rows)
+
+rollback;
+begin;
+alter table alterlock2
+add constraint alterlock2nv foreign key (f1) references alterlock (f1) NOT VALID;
+select * from my_locks order by 1;
+  relname   |       max_lockmode       
+------------+--------------------------
+ alterlock  | ShareUpdateExclusiveLock
+ alterlock2 | ShareRowExclusiveLock
+(2 rows)
+
+commit;
+begin;
+alter table alterlock2 validate constraint alterlock2nv;
+select * from my_locks order by 1;
+     relname     |       max_lockmode       
+-----------------+--------------------------
+ alterlock       | RowShareLock
+ alterlock2      | ShareUpdateExclusiveLock
+ alterlock2_pkey | AccessShareLock
+ alterlock_pkey  | AccessShareLock
+(4 rows)
+
+rollback;
 -- cleanup
+drop table alterlock2;
 drop table alterlock;
 drop view my_locks;
 drop type lockmodes;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 87973c1..a2ad863 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1283,6 +1283,9 @@ and c.relname != 'my_locks'
 group by c.relname;
 
 create table alterlock (f1 int primary key, f2 text);
+insert into alterlock values (1, 'foo');
+create table alterlock2 (f3 int primary key, f1 int);
+insert into alterlock2 values (1, 1);
 
 begin; alter table alterlock alter column f2 set statistics 150;
 select * from my_locks order by 1;
@@ -1324,7 +1327,33 @@ begin; alter table alterlock alter column f2 set default 'x';
 select * from my_locks order by 1;
 rollback;
 
+begin;
+create trigger ttdummy
+	before delete or update on alterlock
+	for each row
+	execute procedure
+	ttdummy (1, 1);
+select * from my_locks order by 1;
+rollback;
+
+begin;
+select * from my_locks order by 1;
+alter table alterlock2 add foreign key (f1) references alterlock (f1);
+select * from my_locks order by 1;
+rollback;
+
+begin;
+alter table alterlock2
+add constraint alterlock2nv foreign key (f1) references alterlock (f1) NOT VALID;
+select * from my_locks order by 1;
+commit;
+begin;
+alter table alterlock2 validate constraint alterlock2nv;
+select * from my_locks order by 1;
+rollback;
+
 -- cleanup
+drop table alterlock2;
 drop table alterlock;
 drop view my_locks;
 drop type lockmodes;
#57Noah Misch
noah@leadboat.com
In reply to: Simon Riggs (#55)

On Mon, Mar 03, 2014 at 07:19:45PM +0000, Simon Riggs wrote:

On 3 March 2014 18:57, Noah Misch <noah@leadboat.com> wrote:

On Mon, Mar 03, 2014 at 10:19:55AM -0500, Robert Haas wrote:

On Thu, Feb 27, 2014 at 3:12 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

Removing SELECT privilege while running a SELECT would be a different
matter. This is all a matter of definition; we can make up any rules
we like. Doing so is IMHO a separate patch and not something to hold
up the main patch.

So I think this is an interesting point. There are various things
that could go wrong as a result of using the wrong lock level. Worst
would be that the server crashes or corrupts data. A little less bad
would be that sessions error out with inexplicable error conditions,
as in SnapshotNow days. Alternatively, we could just have arguably
wrong behavior, like basing query results on the old version of the
table's metadata even after it's been changed.

I would order the concerns like this:

1. Data corruption
2. Transient, clearly-wrong answers without an error
3. Server crash
4. Catalog logical inconsistency
5. Inexplicable, transient errors
6. Valid behavior capable of surprising more than zero upgraders

I like your model for risk assessment. How can we apply it in detail
in a way that helps us decide? Or do we just go on gut feel?

My experience with mentioning such topics is that without structure it
results in an assessment of "unacceptable risk" just simply because
somebody has mentioned some scary words.

True; it's tough to make use of such a prioritization. By the time you can
confidently assign something to a category, you can probably just fix it.
(Or, in the case of category (6), document/release-note it.)

Just to be clear, that list is not a commentary on the particular patch at
hand. Those are merely the kinds of regressions to look for in a patch
affecting this area of the code.

err, guys, you do realise that changing ownership is staying at
AccessExclusiveLock in this patch?
(and I haven't ever suggested lowering that).

Yep.

--
Noah Misch
EnterpriseDB http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#58Tom Lane
tgl@sss.pgh.pa.us
In reply to: Noah Misch (#57)

Noah Misch <noah@leadboat.com> writes:

Just to be clear, that list is not a commentary on the particular patch at
hand. Those are merely the kinds of regressions to look for in a patch
affecting this area of the code.

A complaint on pgsql-bugs just now reminded me of a specific area that
needs to be looked at hard: how bad are the implications for pg_dump?

Up to now, pg_dump could be reasonably confident that once it had
AccessShareLock on every table it intended to dump, there would be no
schema changes happening on those tables until it got done. This greatly
ameliorates the snapshot-skew problems that arise from its habit of doing
some things for itself and other things via backend-internal functions
(which historically used SnapshotNow and now use a fresh MVCC snapshot,
either way potentially quite newer than the transaction snapshot pg_dump's
own queries will use).

I suspect that lowering the lock requirements for anything that affects
what pg_dump can see is going to make things a whole lot worse in terms of
consistency of pg_dump output in the face of concurrent DDL. Admittedly,
we're not perfect in that area now, but I'm not sure that's an adequate
excuse for making it worse.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#59Andres Freund
andres@2ndquadrant.com
In reply to: Tom Lane (#58)

On 2014-03-03 19:15:27 -0500, Tom Lane wrote:

Noah Misch <noah@leadboat.com> writes:

Just to be clear, that list is not a commentary on the particular patch at
hand. Those are merely the kinds of regressions to look for in a patch
affecting this area of the code.

A complaint on pgsql-bugs just now reminded me of a specific area that
needs to be looked at hard: how bad are the implications for pg_dump?

Up to now, pg_dump could be reasonably confident that once it had
AccessShareLock on every table it intended to dump, there would be no
schema changes happening on those tables until it got done.

The guarantee wasn't actually that strong. It already was quite possible
that indexes got created/dropped during that time, which probably is the
by far most frequent DDL run in production.

This greatly
ameliorates the snapshot-skew problems that arise from its habit of doing
some things for itself and other things via backend-internal functions
(which historically used SnapshotNow and now use a fresh MVCC snapshot,
either way potentially quite newer than the transaction snapshot pg_dump's
own queries will use).

Yea, I wonder if we shouldn't start to make them use a different
snapshot. It's the pg_get_*def() functions, or is there something else?

Afair (I really haven't rechecked) all the actions that have a changed
locklevels affect things that pg_dump recreates clientside, using a
repeatable read snapshot, so there shouldn't be much change there?

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#60Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#59)

Andres Freund <andres@2ndquadrant.com> writes:

On 2014-03-03 19:15:27 -0500, Tom Lane wrote:

This greatly
ameliorates the snapshot-skew problems that arise from its habit of doing
some things for itself and other things via backend-internal functions
(which historically used SnapshotNow and now use a fresh MVCC snapshot,
either way potentially quite newer than the transaction snapshot pg_dump's
own queries will use).

Yea, I wonder if we shouldn't start to make them use a different
snapshot. It's the pg_get_*def() functions, or is there something else?

See past discussions. By the time you trace all of ruleutils.c's
dependencies, a dauntingly large fraction of the backend's basic catalog
access support is implicated. For example, you'd need some way of making
the catcaches return data that they know to be outdated. And I'm pretty
sure pg_dump is making free use of stuff that isn't even in ruleutils.

I would like to see pg_dump doing something much more bulletproof, but
I'm afraid that there isn't any nice simple fix available.

One relatively narrow fix that would probably make it a lot better
*in the current state of affairs* is to provide a way whereby, once
pg_dump has locks on everything it wants to dump, it can advance
its transaction snapshot to current time. Then at least both its own
queries and the backend's lookups will see post-locking state. However,
if AccessShareLock isn't enough to block DDL on the tables then we're
still hosed.

Afair (I really haven't rechecked) all the actions that have a changed
locklevels affect things that pg_dump recreates clientside, using a
repeatable read snapshot, so there shouldn't be much change there?

You're missing the point entirely if you think pg_dump recreates
everything client-side. It's never done that 100%, and we've migrated
more and more stuff to the server side over time to avoid duplicate
implementations of things like index expression decompilation. So while
a theoretical answer would be to remove all of pg_dump's dependency on
server-side functions, I am pretty sure that's never going to happen.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#61Andres Freund
andres@2ndquadrant.com
In reply to: Tom Lane (#60)

On 2014-03-03 20:32:13 -0500, Tom Lane wrote:

Afair (I really haven't rechecked) all the actions that have a changed
locklevels affect things that pg_dump recreates clientside, using a
repeatable read snapshot, so there shouldn't be much change there?

You're missing the point entirely if you think pg_dump recreates
everything client-side.

No, I am not obviously not thinking that. What I mean is that the things
that actually change their locking requirement in the proposed patch
primarily influence things that are reconstructed clientside by
pg_dump. E.g ALTER TABLE ... CLUSTER ON, SET(...), ...

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#62Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#61)

Andres Freund <andres@2ndquadrant.com> writes:

On 2014-03-03 20:32:13 -0500, Tom Lane wrote:

You're missing the point entirely if you think pg_dump recreates
everything client-side.

No, I am not obviously not thinking that. What I mean is that the things
that actually change their locking requirement in the proposed patch
primarily influence things that are reconstructed clientside by
pg_dump. E.g ALTER TABLE ... CLUSTER ON, SET(...), ...

[ raised eyebrow... ] I'm pretty sure that no such constraint was
part of the design discussion to start with. Even if it accidentally
happens to be the case now, it sounds utterly fragile.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#63Simon Riggs
simon@2ndQuadrant.com
In reply to: Andres Freund (#59)

On 4 March 2014 01:07, Andres Freund <andres@2ndquadrant.com> wrote:

On 2014-03-03 19:15:27 -0500, Tom Lane wrote:

Noah Misch <noah@leadboat.com> writes:

Just to be clear, that list is not a commentary on the particular patch at
hand. Those are merely the kinds of regressions to look for in a patch
affecting this area of the code.

A complaint on pgsql-bugs just now reminded me of a specific area that
needs to be looked at hard: how bad are the implications for pg_dump?

Up to now, pg_dump could be reasonably confident that once it had
AccessShareLock on every table it intended to dump, there would be no
schema changes happening on those tables until it got done.

The guarantee wasn't actually that strong. It already was quite possible
that indexes got created/dropped during that time, which probably is the
by far most frequent DDL run in production.

Good points.

In most cases, DDL is applied manually after careful thought, so
people seldom dump at the same time they upgrade the database. This is
especially true for pg_dump since it captures the logical definition
of tables. So most people will be happy with the default locking, but
we could make the lock level optional.

Currently we use AccessShareLock. Locking out all DDL, even with this
patch applied would only require ShareUpdateExclusiveLock.

Looking at the code, it will take about an hour to add an option to
pg_dump that specifies the lock level used when dumping. I would be
happy to include that as part of this patch.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#64Atri Sharma
atri.jiit@gmail.com
In reply to: Simon Riggs (#63)

Good points.

In most cases, DDL is applied manually after careful thought, so
people seldom dump at the same time they upgrade the database. This is
especially true for pg_dump since it captures the logical definition
of tables. So most people will be happy with the default locking, but
we could make the lock level optional.

Currently we use AccessShareLock. Locking out all DDL, even with this
patch applied would only require ShareUpdateExclusiveLock.

Looking at the code, it will take about an hour to add an option to
pg_dump that specifies the lock level used when dumping. I would be
happy to include that as part of this patch.

I think the use case for specifying multiple locks is pretty slim given
that a ShareUpdateExclusiveLock is good enough mostly for everybody.

If its not the case, the user should be more careful about when he is
scheduling backups to so that they dont conflict with DDL changes.

I am not too comfortable with exposing the locking type to the user. That
may be just me though.

Regards,

Atri
--
Regards,

Atri
*l'apprenant*

#65Simon Riggs
simon@2ndQuadrant.com
In reply to: Atri Sharma (#64)

On 4 March 2014 08:39, Atri Sharma <atri.jiit@gmail.com> wrote:

Good points.

In most cases, DDL is applied manually after careful thought, so
people seldom dump at the same time they upgrade the database. This is
especially true for pg_dump since it captures the logical definition
of tables. So most people will be happy with the default locking, but
we could make the lock level optional.

Currently we use AccessShareLock. Locking out all DDL, even with this
patch applied would only require ShareUpdateExclusiveLock.

Looking at the code, it will take about an hour to add an option to
pg_dump that specifies the lock level used when dumping. I would be
happy to include that as part of this patch.

I think the use case for specifying multiple locks is pretty slim given that
a ShareUpdateExclusiveLock is good enough mostly for everybody.

Increasing the lock strength would be a change in behaviour that might
adversely affect existing users.

If its not the case, the user should be more careful about when he is
scheduling backups to so that they dont conflict with DDL changes.

That is most certainly the wise choice.

I am not too comfortable with exposing the locking type to the user. That
may be just me though.

Why would that be a problem? Hard reasons, please.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#66Atri Sharma
atri.jiit@gmail.com
In reply to: Simon Riggs (#65)

If its not the case, the user should be more careful about when he is
scheduling backups to so that they dont conflict with DDL changes.

That is most certainly the wise choice.

I am not too comfortable with exposing the locking type to the user. That
may be just me though.

Why would that be a problem? Hard reasons, please.

Should we genuinely depend on the user's good judgement to decide the
locking types?
--
Regards,

Atri
*l'apprenant*

#67Simon Riggs
simon@2ndQuadrant.com
In reply to: Simon Riggs (#65)

On 4 March 2014 09:31, Simon Riggs <simon@2ndquadrant.com> wrote:

On 4 March 2014 08:39, Atri Sharma <atri.jiit@gmail.com> wrote:

Good points.

In most cases, DDL is applied manually after careful thought, so
people seldom dump at the same time they upgrade the database. This is
especially true for pg_dump since it captures the logical definition
of tables. So most people will be happy with the default locking, but
we could make the lock level optional.

Currently we use AccessShareLock. Locking out all DDL, even with this
patch applied would only require ShareUpdateExclusiveLock.

Looking at the code, it will take about an hour to add an option to
pg_dump that specifies the lock level used when dumping. I would be
happy to include that as part of this patch.

I think the use case for specifying multiple locks is pretty slim given that
a ShareUpdateExclusiveLock is good enough mostly for everybody.

Increasing the lock strength would be a change in behaviour that might
adversely affect existing users.

The main impact I see is that this would block VACUUM while pg_dump runs.

But then, while pg_dump runs VACUUM is ineffective anyway so perhaps
that is no bad thing.

Autovacuum requests VACOPT_NOWAIT so would skip the relations being
dumped rather than waiting.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#68Robert Haas
robertmhaas@gmail.com
In reply to: Simon Riggs (#67)

On Tue, Mar 4, 2014 at 6:57 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

The main impact I see is that this would block VACUUM while pg_dump runs.

But then, while pg_dump runs VACUUM is ineffective anyway so perhaps
that is no bad thing.

Well, a vacuum that's already running when pg_dump starts up may be
doing a lot of good, so it would be a shame to see pg_dump kill them
all off.

Also, this would put us in the surprising situation that you can't run
two simultaneous dumps of overlapping sets of tables, which doesn't
strike me as a great thing.

I'd really like to see us find a way to apply some version of this
patch. I was in favor of the concept 3 years ago when we did this the
first time, and I've subsequently done quite a bit of work (viz., MVCC
catalog snapshots) to eliminate the main objection that was raised at
that time. But it's really hard to reason about what might happen
with lowered lock levels, and convince yourself that there's
absolutely nothing that can ever go wrong. I don't know what to do
about that tension, but I think even modest improvements in this area
stand to benefit an awful lot of users.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#69Atri Sharma
atri.jiit@gmail.com
In reply to: Robert Haas (#68)

I'd really like to see us find a way to apply some version of this

patch. I was in favor of the concept 3 years ago when we did this the
first time, and I've subsequently done quite a bit of work (viz., MVCC
catalog snapshots) to eliminate the main objection that was raised at
that time. But it's really hard to reason about what might happen
with lowered lock levels, and convince yourself that there's
absolutely nothing that can ever go wrong. I don't know what to do
about that tension, but I think even modest improvements in this area
stand to benefit an awful lot of users.

Wouldnt MVCC's strictness rules pose harder restrictions on pg_dump instead
of relaxing them?

Regards,

Atri

--
Regards,

Atri
*l'apprenant*

#70Simon Riggs
simon@2ndQuadrant.com
In reply to: Robert Haas (#68)

On 4 March 2014 12:18, Robert Haas <robertmhaas@gmail.com> wrote:

On Tue, Mar 4, 2014 at 6:57 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

The main impact I see is that this would block VACUUM while pg_dump runs.

But then, while pg_dump runs VACUUM is ineffective anyway so perhaps
that is no bad thing.

Well, a vacuum that's already running when pg_dump starts up may be
doing a lot of good, so it would be a shame to see pg_dump kill them
all off.

Also, this would put us in the surprising situation that you can't run
two simultaneous dumps of overlapping sets of tables, which doesn't
strike me as a great thing.

These changes in concurrency are the most serious objections and a
definite change in previous behaviour. So we cannot pick a single lock
level that suits all goals the user may have.

I'd really like to see us find a way to apply some version of this
patch. I was in favor of the concept 3 years ago when we did this the
first time, and I've subsequently done quite a bit of work (viz., MVCC
catalog snapshots) to eliminate the main objection that was raised at
that time. But it's really hard to reason about what might happen
with lowered lock levels, and convince yourself that there's
absolutely nothing that can ever go wrong. I don't know what to do
about that tension, but I think even modest improvements in this area
stand to benefit an awful lot of users.

Agreed. The question is, which subset? The issue just raised would
affect whichever subset we choose, so reducing the scope of the patch
does nothing to the impact of the pg_dump issue.

I will add the option to change lock level for pg_dump. It's simple to
use, clear as to why it would be needed and effective at removing this
as an obstacle.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#71Stephen Frost
sfrost@snowman.net
In reply to: Atri Sharma (#64)

* Atri Sharma (atri.jiit@gmail.com) wrote:

If its not the case, the user should be more careful about when he is
scheduling backups to so that they dont conflict with DDL changes.

I'm not following this as closely as I'd like to, but I wanted to voice
my opinion that this is just not acceptable as a general answer. There
are a good many applications out there which do DDL as part of ongoing
activity (part of ETL, or something else) and still need to be able to
get a pg_dump done. It's not a design I'd recommend, but I don't think
we get to just write it off either.

Thanks,

Stephen

#72Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Stephen Frost (#71)

Stephen Frost escribi�:

* Atri Sharma (atri.jiit@gmail.com) wrote:

If its not the case, the user should be more careful about when he is
scheduling backups to so that they dont conflict with DDL changes.

I'm not following this as closely as I'd like to, but I wanted to voice
my opinion that this is just not acceptable as a general answer. There
are a good many applications out there which do DDL as part of ongoing
activity (part of ETL, or something else) and still need to be able to
get a pg_dump done. It's not a design I'd recommend, but I don't think
we get to just write it off either.

Agreed -- "user caution" is a recipe for trouble, because these things
cannot always be planned in minute detail (or such planning creates an
excessive cost.)

One concern is schema changes that make a dump unrestorable, for
instance if there's a foreign key relationship between tables A and B,
such that pg_dump dumps the FK for table A but by the time it dumps
table B the unique index has gone and thus restoring the FK fails.
If this is a realistic failure scenario, then we need some mechanism to
avoid it.

One possible idea would be to create a new lock level which conflicts
with DDL changes but not with regular operation including dumps; so it
wouldn't self-conflict but it would conflict with ShareUpdateExclusive.
pg_dump would acquire a lock of that level instead of AccessShare; thus
two pg_dumps would be able to run on the same table simultaneously, but
it would block and be blocked by DDL changes that grab SUE. The big
hole in this is that pg_dump would still block vacuum, which is a
problem. I hesitate two suggest two extra levels, one for dumps (which
wouldn't conflict with SUE) and one for non-exclusive DDL changes (which
would.)

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#73Atri Sharma
atri.jiit@gmail.com
In reply to: Stephen Frost (#71)

On Tue, Mar 4, 2014 at 8:19 PM, Stephen Frost <sfrost@snowman.net> wrote:

* Atri Sharma (atri.jiit@gmail.com) wrote:

If its not the case, the user should be more careful about when he is
scheduling backups to so that they dont conflict with DDL changes.

I'm not following this as closely as I'd like to, but I wanted to voice
my opinion that this is just not acceptable as a general answer. There
are a good many applications out there which do DDL as part of ongoing
activity (part of ETL, or something else) and still need to be able to
get a pg_dump done. It's not a design I'd recommend, but I don't think
we get to just write it off either.

Well, that will require something like MVCC or stricter locking in general.
That is not in line with the aim of this patch, hence I raised this point.

Regards,

Atri

Regards,

Atri
*l'apprenant*

#74Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#72)

On Tue, Mar 4, 2014 at 10:17 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

One possible idea would be to create a new lock level which conflicts
with DDL changes but not with regular operation including dumps; so it
wouldn't self-conflict but it would conflict with ShareUpdateExclusive.
pg_dump would acquire a lock of that level instead of AccessShare; thus
two pg_dumps would be able to run on the same table simultaneously, but
it would block and be blocked by DDL changes that grab SUE. The big
hole in this is that pg_dump would still block vacuum, which is a
problem. I hesitate two suggest two extra levels, one for dumps (which
wouldn't conflict with SUE) and one for non-exclusive DDL changes (which
would.)

AFAIK, the only reason why vacuum takes ShareUpdateExclusive lock is
because it can't run at the same time as another vacuum. I tend to
think (and have thought for some time) that we really ought to have
vacuum take AccessShareLock on the relation plus some other lock that
is specific to vacuum (say, a "relation vacuum" lock, just as we have
"relation extension" locks).

Your idea of a lock strong enough to conflict with DDL but not
self-conflicting is interesting, too, but I can't claim to have
thought through it all that carefully just yet.

I think this is all too late for 9.4, though.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#75Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#72)

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

One concern is schema changes that make a dump unrestorable, for
instance if there's a foreign key relationship between tables A and B,

Yeah. Ideally, what pg_dump would produce would be a consistent snapshot
of the database state as of its transaction snapshot time. We have always
had that guarantee so far as user data was concerned, but it's been shaky
(and getting worse) so far as the database schema is concerned. What
bothers me about the current patch is that it's going to make it a whole
lot more worse.

Also, I don't have any love at all for proposals that we increase the lock
level that pg_dump holds. pg_dump tends to run for a long time.

I've not been paying all that much attention to the logical-decoding
patches, but wasn't there something in there about being able to see
the catalog state as it was at some point in the past? If so, maybe
we could leverage that to allow a backend to enter a "pg_dump state"
wherein its view of the catalogs was frozen at its transaction start
snapshot. We'd have to restrict it to read-only operation for safety,
but that's surely no problem for pg_dump. If we had that, then this
whole problem of server-side computations producing inconsistent
results would go away.

There might still be a window wherein tables visible at transaction start
could be dropped before AccessShareLock could be acquired, but I think
we could let pg_dump error out in that case.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#76Stephen Frost
sfrost@snowman.net
In reply to: Tom Lane (#75)

* Tom Lane (tgl@sss.pgh.pa.us) wrote:

Yeah. Ideally, what pg_dump would produce would be a consistent snapshot
of the database state as of its transaction snapshot time. We have always
had that guarantee so far as user data was concerned, but it's been shaky
(and getting worse) so far as the database schema is concerned. What
bothers me about the current patch is that it's going to make it a whole
lot more worse.

Also, I don't have any love at all for proposals that we increase the lock
level that pg_dump holds. pg_dump tends to run for a long time.

Agreed.

I've not been paying all that much attention to the logical-decoding
patches, but wasn't there something in there about being able to see
the catalog state as it was at some point in the past? If so, maybe
we could leverage that to allow a backend to enter a "pg_dump state"
wherein its view of the catalogs was frozen at its transaction start
snapshot. We'd have to restrict it to read-only operation for safety,
but that's surely no problem for pg_dump. If we had that, then this
whole problem of server-side computations producing inconsistent
results would go away.

That certainly sounds like a tempting idea.

There might still be a window wherein tables visible at transaction start
could be dropped before AccessShareLock could be acquired, but I think
we could let pg_dump error out in that case.

I don't have too much of an issue with the above, but I would like to
have us figure out a solution to the deadlock problem with parallel
pg_dump. The issue arises when pg_dump gets an AccessShareLock and then
another process attempts to acquire an AccessExclusiveLock, which then
blocks, and then the pg_dump worker process tries to get its
AccessShareLock- we end up not being able to make any progress on
anything at that point.

One suggestion that was discussed at PGConf.EU was having processes
which share the same snapshot (the pg_dump master and worker processes)
able to either share the same locks or at least be able to "jump" the
lock queue (that is, the worker process wouldn't have to wait being the
AEL to get an ASL, since the ASL was already aquired for the snapshot
which was exported and shared with it).

Thanks,

Stephen

#77Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#74)

Robert Haas <robertmhaas@gmail.com> writes:

I think this is all too late for 9.4, though.

I agree with the feeling that a meaningful fix for pg_dump isn't going
to get done for 9.4. So that leaves us with the alternatives of
(1) put off the lock-strength-reduction patch for another year;
(2) push it anyway and accept a reduction in pg_dump reliability.

I don't care for (2). I'd like to have lock strength reduction as
much as anybody, but it can't come at the price of reduction of
reliability.

The bigger picture here is that it seems like anytime I've thought
for more than five minutes about the lock strength reduction patch,
I've come up with some fundamental problem. That doesn't leave me
with a warm feeling that we're getting close to having something
committable.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#78Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Tom Lane (#77)

Tom Lane escribi�:

I'd like to have lock strength reduction as much as anybody, but it
can't come at the price of reduction of reliability.

Can we have at least a cut-down version of it? If we can just reduce
the lock level required for ALTER TABLE / VALIDATE, that would be an
enormous improvement already.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#79Tom Lane
tgl@sss.pgh.pa.us
In reply to: Stephen Frost (#76)

Stephen Frost <sfrost@snowman.net> writes:

I don't have too much of an issue with the above, but I would like to
have us figure out a solution to the deadlock problem with parallel
pg_dump. The issue arises when pg_dump gets an AccessShareLock and then
another process attempts to acquire an AccessExclusiveLock, which then
blocks, and then the pg_dump worker process tries to get its
AccessShareLock- we end up not being able to make any progress on
anything at that point.

The original ASL was acquired by the pg_dump master, right?

One suggestion that was discussed at PGConf.EU was having processes
which share the same snapshot (the pg_dump master and worker processes)
able to either share the same locks or at least be able to "jump" the
lock queue (that is, the worker process wouldn't have to wait being the
AEL to get an ASL, since the ASL was already aquired for the snapshot
which was exported and shared with it).

Yeah, it seems like we need lock export not only snapshot export to make
this work nicely. But that sounds orthogonal to the issues being
discussed in this thread.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#80Stephen Frost
sfrost@snowman.net
In reply to: Tom Lane (#79)

* Tom Lane (tgl@sss.pgh.pa.us) wrote:

Stephen Frost <sfrost@snowman.net> writes:

I don't have too much of an issue with the above, but I would like to
have us figure out a solution to the deadlock problem with parallel
pg_dump. The issue arises when pg_dump gets an AccessShareLock and then
another process attempts to acquire an AccessExclusiveLock, which then
blocks, and then the pg_dump worker process tries to get its
AccessShareLock- we end up not being able to make any progress on
anything at that point.

The original ASL was acquired by the pg_dump master, right?

Yup. It goes through and gets ASLs on everything first.

One suggestion that was discussed at PGConf.EU was having processes
which share the same snapshot (the pg_dump master and worker processes)
able to either share the same locks or at least be able to "jump" the
lock queue (that is, the worker process wouldn't have to wait being the
AEL to get an ASL, since the ASL was already aquired for the snapshot
which was exported and shared with it).

Yeah, it seems like we need lock export not only snapshot export to make
this work nicely. But that sounds orthogonal to the issues being
discussed in this thread.

Indeed, just figured I'd mention it since we're talking about
pg_dump-related locking.

Thanks,

Stephen

#81Atri Sharma
atri.jiit@gmail.com
In reply to: Stephen Frost (#80)

On Tue, Mar 4, 2014 at 10:20 PM, Stephen Frost <sfrost@snowman.net> wrote:

* Tom Lane (tgl@sss.pgh.pa.us) wrote:

Stephen Frost <sfrost@snowman.net> writes:

I don't have too much of an issue with the above, but I would like to
have us figure out a solution to the deadlock problem with parallel
pg_dump. The issue arises when pg_dump gets an AccessShareLock and

then

another process attempts to acquire an AccessExclusiveLock, which then
blocks, and then the pg_dump worker process tries to get its
AccessShareLock- we end up not being able to make any progress on
anything at that point.

The original ASL was acquired by the pg_dump master, right?

Yup. It goes through and gets ASLs on everything first.

One suggestion that was discussed at PGConf.EU was having processes
which share the same snapshot (the pg_dump master and worker processes)
able to either share the same locks or at least be able to "jump" the
lock queue (that is, the worker process wouldn't have to wait being the
AEL to get an ASL, since the ASL was already aquired for the snapshot
which was exported and shared with it).

Yeah, it seems like we need lock export not only snapshot export to make
this work nicely. But that sounds orthogonal to the issues being
discussed in this thread.

Indeed, just figured I'd mention it since we're talking about
pg_dump-related locking.

What happens for foreign key constraints? For pg_dump, do we lock the
tables referenced by the table which is being dumped right now? If that is
the case, wouldnt MVCC based approach be the best way for this?

Please ignore if I said anything silly. I am just trying to understand how
it works here.

Regards,

Atri

#82Andres Freund
andres@2ndquadrant.com
In reply to: Tom Lane (#77)
Re: ALTER TABLE lock strength reduction patch is unsafe Reply-To:

On 2014-03-04 11:40:10 -0500, Tom Lane wrote:

Robert Haas <robertmhaas@gmail.com> writes: > I think this is all too
late for 9.4, though.

I agree with the feeling that a meaningful fix for pg_dump isn't going
to get done for 9.4. So that leaves us with the alternatives of (1)
put off the lock-strength-reduction patch for another year; (2) push
it anyway and accept a reduction in pg_dump reliability.

I don't care for (2). I'd like to have lock strength reduction as
much as anybody, but it can't come at the price of reduction of
reliability.

I am sorry, but I think this is vastly overstating the scope of the
pg_dump problem. CREATE INDEX *already* doesn't require a AEL, and the
amount of problems that has caused in the past is surprisingly low. If
such a frequently used command doesn't cause problems, why are you
assuming other commands to be that problematic? And I think it's hard to
argue that the proposed changes are more likely to cause problems.

Let's try to go at this a bit more methodically. The commands that -
afaics - change their locklevel due to latest patch (v21) are:

01) ALTER TABLE .. ADD CONSTRAINT
02) ALTER TABLE .. ADD CONSTRAINT ... USING
03) ALTER TABLE .. ENABLE | DISABLE [ REPLICA | ALWAYS ] TRIGGER [ ALL ]
04) ALTER TABLE .. ALTER CONSTRAINT
05) ALTER TABLE .. REPLICA IDENTITY
06) ALTER TABLE .. ALTER COLUMN .. SET NOT NULL (*not* DROP NULL)
cmd_lockmode = ShareRowExclusiveLock;

07) ALTER TABLE ... ALTER COLUMN ... SET STATISTICS
08) ALTER TABLE ... CLUSTER ON ...
09) ALTER TABLE ... SET WITHOUT CLUSTER
10) ALTER TABLE ... SET (...)
11) ALTER TABLE ... RESET (...)
12) ALTER TABLE ... ALTER COLUMN ... SET (...)
13) ALTER TABLE ... ALTER COLUMN ... RESET (...)
14) ALTER TABLE ... VALIDATE CONSTRAINT constraint_name
cmd_lockmode = ShareUpdateExclusiveLock;

I have my reservations about ADD CONSTRAINT (including SET NOT NULL)
being unproblematic (mostly because I haven't thought through possible
consquences for the planner making different choices with added
constraints).

From the perspective of pg_dump consistency, except ADD CONSTRAINT, none
of those seem to have graver possible consequences than CREATE INDEX
(and DROP INDEX CONCURRENTLY) already being unsafe.

In fact all of those should actually end up being *safer*, even ending
up always being dumped consistently since they are all reconstructed
clientside by pg_dump.
You argue elsewhere that that's a fragile coincidence. But so what, even
if it changes, the consequences still are going to be *far* less
significant than missing various index, trigger, and whatnot commands.

I think the set of problems you mention are going to be really important
when we someday get around to make stuff like ALTER TABLE ... ADD/DROP
COLUMN require lower lock levels, but that's not what's proposed.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7
Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#83Simon Riggs
simon@2ndQuadrant.com
In reply to: Tom Lane (#75)

On 4 March 2014 16:27, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

One concern is schema changes that make a dump unrestorable, for
instance if there's a foreign key relationship between tables A and B,

Yeah. Ideally, what pg_dump would produce would be a consistent snapshot
of the database state as of its transaction snapshot time. We have always
had that guarantee so far as user data was concerned, but it's been shaky
(and getting worse) so far as the database schema is concerned. What
bothers me about the current patch is that it's going to make it a whole
lot more worse.

While thinking this through it occurs to me that there is no problem at all.

Your earlier claim that the dump is inconsistent just isn't accurate.
We now have MVCC catalogs, so any dump is going to see a perfectly
consistent set of data plus DDL. OK the catalogs may change AFTER the
snapshot was taken for the dump, but then so can the data change -
that's just MVCC.

I was going to add an option to increase lock level, but I can't see
why you'd want it even. The dumps are consistent...

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#84Andres Freund
andres@2ndquadrant.com
In reply to: Simon Riggs (#83)

On March 4, 2014 8:39:55 PM CET, Simon Riggs <simon@2ndQuadrant.com> wrote:

On 4 March 2014 16:27, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

One concern is schema changes that make a dump unrestorable, for
instance if there's a foreign key relationship between tables A and

B,

Yeah. Ideally, what pg_dump would produce would be a consistent

snapshot

of the database state as of its transaction snapshot time. We have

always

had that guarantee so far as user data was concerned, but it's been

shaky

(and getting worse) so far as the database schema is concerned. What
bothers me about the current patch is that it's going to make it a

whole

lot more worse.

While thinking this through it occurs to me that there is no problem at
all.

Your earlier claim that the dump is inconsistent just isn't accurate.
We now have MVCC catalogs, so any dump is going to see a perfectly
consistent set of data plus DDL. OK the catalogs may change AFTER the
snapshot was taken for the dump, but then so can the data change -
that's just MVCC.

I was going to add an option to increase lock level, but I can't see
why you'd want it even. The dumps are consistent...

Mvcc scans only guarantee that individual scans are consistent, not that separate scans are. Each individual scan takes a new snapshot if there's been ddl.

Andres

--
Please excuse brevity and formatting - I am writing this on my mobile phone.

Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#85Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#82)
Re: ALTER TABLE lock strength reduction patch is unsafe Reply-To:

Andres Freund <andres@2ndquadrant.com> writes:

On 2014-03-04 11:40:10 -0500, Tom Lane wrote:

I don't care for (2). I'd like to have lock strength reduction as
much as anybody, but it can't come at the price of reduction of
reliability.

I am sorry, but I think this is vastly overstating the scope of the
pg_dump problem. CREATE INDEX *already* doesn't require a AEL, and the
amount of problems that has caused in the past is surprisingly low.

CREATE INDEX happens to be okay because pg_dump won't try to dump indexes
it doesn't see in its snapshot, ie the list of indexes to dump is created
client-side. CREATE INDEX CONCURRENTLY, otoh, already did break pg_dump,
and we had to hack things to fix it; see commit
683abc73dff549e94555d4020dae8d02f32ed78b.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#86Robert Haas
robertmhaas@gmail.com
In reply to: Simon Riggs (#83)

On Tue, Mar 4, 2014 at 2:39 PM, Simon Riggs <simon@2ndquadrant.com> wrote:

On 4 March 2014 16:27, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

One concern is schema changes that make a dump unrestorable, for
instance if there's a foreign key relationship between tables A and B,

Yeah. Ideally, what pg_dump would produce would be a consistent snapshot
of the database state as of its transaction snapshot time. We have always
had that guarantee so far as user data was concerned, but it's been shaky
(and getting worse) so far as the database schema is concerned. What
bothers me about the current patch is that it's going to make it a whole
lot more worse.

While thinking this through it occurs to me that there is no problem at all.

Your earlier claim that the dump is inconsistent just isn't accurate.
We now have MVCC catalogs, so any dump is going to see a perfectly
consistent set of data plus DDL. OK the catalogs may change AFTER the
snapshot was taken for the dump, but then so can the data change -
that's just MVCC.

Unfortunately, this isn't correct. The MVCC snapshots taken for
catalog scans are "instantaneous"; that is, we take a new, current
snapshot for each catalog scan. If all of the ruleutils.c stuff were
using the transaction snapshot rather than instantaneous snapshots,
this would be right. But as has been previously discussed, that's not
the case.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#87Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#86)

Robert Haas <robertmhaas@gmail.com> writes:

On Tue, Mar 4, 2014 at 2:39 PM, Simon Riggs <simon@2ndquadrant.com> wrote:

Your earlier claim that the dump is inconsistent just isn't accurate.
We now have MVCC catalogs, so any dump is going to see a perfectly
consistent set of data plus DDL. OK the catalogs may change AFTER the
snapshot was taken for the dump, but then so can the data change -
that's just MVCC.

Unfortunately, this isn't correct. The MVCC snapshots taken for
catalog scans are "instantaneous"; that is, we take a new, current
snapshot for each catalog scan. If all of the ruleutils.c stuff were
using the transaction snapshot rather than instantaneous snapshots,
this would be right. But as has been previously discussed, that's not
the case.

Yeah. And that's *necessary* for catalog lookups in a normally
functioning backend, because we have to see latest data (eg, it wouldn't
do for a backend to fail to enforce a just-added CHECK constraint because
it was committed after the backend's transaction started).

However, it seems possible that we could have a mode in which a read-only
session did all its catalog fetches according to the transaction snapshot.
That would get us to a situation where the backend-internal lookups that
ruleutils relies on would give the same answers as queries done by
pg_dump. Robert's work on getting rid of SnapshotNow has probably moved
that much closer than it was before, but it's still not exactly a trivial
patch.

Meanwhile, Andres claimed upthread that none of the currently-proposed
reduced-lock ALTER commands affect data that pg_dump is using ruleutils
to fetch. If that's the case, then maybe this is a problem that we can
punt till later. I've not gone through the list to verify it though.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#88Josh Berkus
josh@agliodbs.com
In reply to: Simon Riggs (#41)

On 03/04/2014 11:43 AM, Andres Freund wrote:

On March 4, 2014 8:39:55 PM CET, Simon Riggs <simon@2ndQuadrant.com> wrote:

I was going to add an option to increase lock level, but I can't see
why you'd want it even. The dumps are consistent...

Mvcc scans only guarantee that individual scans are consistent, not that separate scans are. Each individual scan takes a new snapshot if there's been ddl.

Andres

I thought that we were sharing the same snapshot, for parallel dump?

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#89Andres Freund
andres@2ndquadrant.com
In reply to: Josh Berkus (#88)

On 2014-03-04 14:29:31 -0800, Josh Berkus wrote:

On 03/04/2014 11:43 AM, Andres Freund wrote:

On March 4, 2014 8:39:55 PM CET, Simon Riggs <simon@2ndQuadrant.com> wrote:

I was going to add an option to increase lock level, but I can't see
why you'd want it even. The dumps are consistent...

Mvcc scans only guarantee that individual scans are consistent, not that separate scans are. Each individual scan takes a new snapshot if there's been ddl.

I thought that we were sharing the same snapshot, for parallel dump?

That snapshot is about data, not the catalog. And no, we can't easily
reuse one for the other, see elsewhere in this thread for some of the
reasons.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#90Greg Stark
stark@mit.edu
In reply to: Tom Lane (#85)
Re: ALTER TABLE lock strength reduction patch is unsafe Reply-To:

On Tue, Mar 4, 2014 at 8:08 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

CREATE INDEX CONCURRENTLY, otoh, already did break pg_dump,
and we had to hack things to fix it; see commit
683abc73dff549e94555d4020dae8d02f32ed78b.

Well pg_dump was only broken in that there was a new catalog state to
deal with. But the commit you linked to was fixing pg_upgrade which
was broken because the on-disk schema was then out of sync with what
pg_dump would generate. But that should only matter for creating or
deleting whole relations.
--
greg

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#91Andres Freund
andres@2ndquadrant.com
In reply to: Tom Lane (#87)

On 2014-03-04 16:37:48 -0500, Tom Lane wrote:

However, it seems possible that we could have a mode in which a read-only
session did all its catalog fetches according to the transaction snapshot.
That would get us to a situation where the backend-internal lookups that
ruleutils relies on would give the same answers as queries done by
pg_dump. Robert's work on getting rid of SnapshotNow has probably moved
that much closer than it was before, but it's still not exactly a trivial
patch.

The most interesting bit seems to be the cache invalidation handling. It
would need to be something like PushCatalogSnapshot() which disables
applying invals, including catchup interrupts and all. If the sinval
queue hasn't overflown while that snapshot was up, everything is fine we
just need to apply all pending invalidations, if it has, we need to do a
InvalidateSystemCaches().

Meanwhile, Andres claimed upthread that none of the currently-proposed
reduced-lock ALTER commands affect data that pg_dump is using ruleutils
to fetch. If that's the case, then maybe this is a problem that we can
punt till later. I've not gone through the list to verify it though.

I think it's true for all but T_AddConstraint, but I am wary about that
one anyway. But somebody else should definitely check the list.

I wonder if AddConstraintUsing would possibly be safe...

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#92Tom Lane
tgl@sss.pgh.pa.us
In reply to: Greg Stark (#90)
Re: ALTER TABLE lock strength reduction patch is unsafe Reply-To:

Greg Stark <stark@mit.edu> writes:

On Tue, Mar 4, 2014 at 8:08 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

CREATE INDEX CONCURRENTLY, otoh, already did break pg_dump,
and we had to hack things to fix it; see commit
683abc73dff549e94555d4020dae8d02f32ed78b.

Well pg_dump was only broken in that there was a new catalog state to
deal with. But the commit you linked to was fixing pg_upgrade which
was broken because the on-disk schema was then out of sync with what
pg_dump would generate.

No, it was fixing cases that would cause problems with or without
pg_upgrade. Arguably that patch made it worse for pg_upgrade, which
needed a followon patch (203d8ae2d).

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#93Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#91)

Andres Freund <andres@2ndquadrant.com> writes:

On 2014-03-04 16:37:48 -0500, Tom Lane wrote:

However, it seems possible that we could have a mode in which a read-only
session did all its catalog fetches according to the transaction snapshot.
That would get us to a situation where the backend-internal lookups that
ruleutils relies on would give the same answers as queries done by
pg_dump. Robert's work on getting rid of SnapshotNow has probably moved
that much closer than it was before, but it's still not exactly a trivial
patch.

The most interesting bit seems to be the cache invalidation handling. It
would need to be something like PushCatalogSnapshot() which disables
applying invals, including catchup interrupts and all. If the sinval
queue hasn't overflown while that snapshot was up, everything is fine we
just need to apply all pending invalidations, if it has, we need to do a
InvalidateSystemCaches().

Yeah, at least within the transaction we would simply ignore invals.
To avoid causing sinval queue overrun (which would hurt everyone
system-wide), my inclination would be to just drop invals on the floor all
the time when in this mode, and forcibly do InvalidateSystemCaches at
transaction end. For pg_dump, at least, there is no value in working any
harder than that, since it's going to quit at transaction end anyhow.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#94Simon Riggs
simon@2ndQuadrant.com
In reply to: Tom Lane (#87)
1 attachment(s)

On 4 March 2014 21:37, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Tue, Mar 4, 2014 at 2:39 PM, Simon Riggs <simon@2ndquadrant.com> wrote:

Your earlier claim that the dump is inconsistent just isn't accurate.
We now have MVCC catalogs, so any dump is going to see a perfectly
consistent set of data plus DDL. OK the catalogs may change AFTER the
snapshot was taken for the dump, but then so can the data change -
that's just MVCC.

Unfortunately, this isn't correct. The MVCC snapshots taken for
catalog scans are "instantaneous"; that is, we take a new, current
snapshot for each catalog scan. If all of the ruleutils.c stuff were
using the transaction snapshot rather than instantaneous snapshots,
this would be right. But as has been previously discussed, that's not
the case.

Yeah. And that's *necessary* for catalog lookups in a normally
functioning backend, because we have to see latest data (eg, it wouldn't
do for a backend to fail to enforce a just-added CHECK constraint because
it was committed after the backend's transaction started).

OK, thanks for explaining. A valuable point to note for us all.

However, it seems possible that we could have a mode in which a read-only
session did all its catalog fetches according to the transaction snapshot.
That would get us to a situation where the backend-internal lookups that
ruleutils relies on would give the same answers as queries done by
pg_dump. Robert's work on getting rid of SnapshotNow has probably moved
that much closer than it was before, but it's still not exactly a trivial
patch.

Meanwhile, Andres claimed upthread that none of the currently-proposed
reduced-lock ALTER commands affect data that pg_dump is using ruleutils
to fetch. If that's the case, then maybe this is a problem that we can
punt till later. I've not gone through the list to verify it though.

So that returns us to solving the catalog consistency problem in
pg_dump and similar applications.

We could

(1) change the lock taken by pg_dump to be ShareUpdateExclusive. As
discussed, this would be optional. (Trivial implementation)

The catalog accesses are all in a rather isolated piece of code in
pg_dump and run for a short period. That allows us to consider locking
*always* at ShareUpdateExclusive but only for the period of catalog
access and then release the higher level lock before transaction end.
Since pg_dump is a client program any action we take to resolve this
would need to be done in a user accessible way. That is acceptable
since there may be other user programs that wish/need to read a
consistent view of the definition of a table. This can be implemented
in a few ways:

(2) Implement a server function that allows you to lock a table for a
short duration. e.g. pg_lock_catalog(Oid) and pg_unlock_catalog(Oid).
We can already do this in server-side code, so this is simply a matter
of exposing that same functionality for users.

(3) A new variant of the LOCK command: LOCK CATALOG FOR tablename IN
lock mode MODE NOWAIT, which then would have a matching UNLOCK CATALOG
FOR tablename command. This is just a sugar coated version of (2).

(4) Implement functions to suspend invalidation message handling for a
short period. That's basically the same as (2) in profile. My feeling
is that sounds rather dangerous and not something I'd want to go near
now in in the future.

We tried to avoid locking the catalog some years back, which is how we
went off down this MVCC catalog access, which now seems to have been
something of a red-shifted herring. ISTM that the user would need to
specifically request a "consistent catalog".

Using (2) in pg_dump is pretty easy - patch attached. So we can solve
this problem completely in about another 1 hour of work, so I suggest
we implement (2) and be done.

Happy to document this in a new subsection of docs to describe how to
dump a consistent view of a database object from a user application.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

lock_catalog_for_pgdump.v1.patchapplication/octet-stream; name=lock_catalog_for_pgdump.v1.patchDownload
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 4d35ae5..06c8647 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -244,6 +244,9 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "reading rewrite rules\n");
 	getRules(fout, &numRules);
 
+	/* Do this last... */
+	unlockCatalogs(fout, tblinfo, numTables);
+
 	*numTablesPtr = numTables;
 	return tblinfo;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4fabc1d..f5f382f 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4797,6 +4797,18 @@ getTables(Archive *fout, int *numTables)
 		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION)
 		{
 			resetPQExpBuffer(query);
+
+			/*
+			 * Lock the catalog for the objects first. This lock will
+			 * be released when we have all catalog information, leaving
+			 * the AccessShareLock to persist while we dump data (if any).
+			 */
+			if (fout->remoteVersion >= 90400)
+				appendPQExpBuffer(query,
+							  "SELECT pg_lock_catalog('%s');",
+							  fmtQualifiedId(fout->remoteVersion,
+										tblinfo[i].dobj.namespace->dobj.name,
+											 tblinfo[i].dobj.name));
 			appendPQExpBuffer(query,
 							  "LOCK TABLE %s IN ACCESS SHARE MODE",
 							  fmtQualifiedId(fout->remoteVersion,
@@ -4824,6 +4836,49 @@ getTables(Archive *fout, int *numTables)
 }
 
 /*
+ * unlockCatalogs
+ *
+ * Catalogs are locked for dumpable objects in getTables()
+ * unlockCatalogs() is called right at the end of catalog access
+ */
+void
+unlockCatalogs(Archive *fout, TableInfo tblinfo[], int numTables)
+{
+	PGresult   *res;
+	int			i;
+	PQExpBuffer query;
+
+	if (fout->remoteVersion < 90400)
+		return;
+
+	query = createPQExpBuffer();
+
+	for (i = 0; i < numTables; i++)
+	{
+		/*
+		 * Ensure this matches the list of objects locked in getTables()
+		 */
+		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION)
+		{
+			resetPQExpBuffer(query);
+
+			/*
+			 * Unlock the catalog for the objects.
+			 */
+			appendPQExpBuffer(query,
+							  "SELECT pg_unlock_catalog('%s');",
+							  fmtQualifiedId(fout->remoteVersion,
+										tblinfo[i].dobj.namespace->dobj.name,
+											 tblinfo[i].dobj.name));
+			ExecuteSqlStatement(fout, query->data);
+		}
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+}
+/*
  * getOwnedSeqs
  *	  identify owned sequences and mark them as dumpable if owning table is
  *
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index c8dac35..e371d0a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -553,6 +553,7 @@ extern OpfamilyInfo *getOpfamilies(Archive *fout, int *numOpfamilies);
 extern CollInfo *getCollations(Archive *fout, int *numCollations);
 extern ConvInfo *getConversions(Archive *fout, int *numConversions);
 extern TableInfo *getTables(Archive *fout, int *numTables);
+extern void unlockCatalogs(Archive *fout, TableInfo tblinfo[], int numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
 extern InhInfo *getInherits(Archive *fout, int *numInherits);
 extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
#95Simon Riggs
simon@2ndQuadrant.com
In reply to: Simon Riggs (#94)

On 5 March 2014 09:28, Simon Riggs <simon@2ndquadrant.com> wrote:

So that returns us to solving the catalog consistency problem in
pg_dump and similar applications.

No answer, guys, and time is ticking away here.

I'd like to get a communal solution to this rather than just punting
the whole patch.

If we have to strip it down to the bar essentials, so be it. For me,
the biggest need here is to make VALIDATE CONSTRAINT take only a
ShareUpdateExclusiveLock while it runs. Almost everything else needs a
full AccessExclusiveLock anyway, or doesn't run for very long so isn't
a critical problem. (Perhaps we can then wrap ADD CONSTRAINT ... NOT
VALID and VALIDATE into a single command using the CONCURRENTLY
keyword so it runs two transactions to complete the task).

Validating FKs on big tables can take hours and it really isn't
acceptable for us to lock out access while we do that. FKs are
*supposed* to be a major reason people use RDBMS, so keeping them in a
state where they are effectively unusable is a major debilitating
point against adoption of PostgreSQL.

If there are issues with pg_dump we can just document them.

Guide me with your thoughts.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#96Noah Misch
noah@leadboat.com
In reply to: Andres Freund (#82)
Re: ALTER TABLE lock strength reduction patch is unsafe Reply-To:

On Tue, Mar 04, 2014 at 06:50:17PM +0100, Andres Freund wrote:

On 2014-03-04 11:40:10 -0500, Tom Lane wrote:

Robert Haas <robertmhaas@gmail.com> writes: > I think this is all too
late for 9.4, though.

I agree with the feeling that a meaningful fix for pg_dump isn't going
to get done for 9.4. So that leaves us with the alternatives of (1)
put off the lock-strength-reduction patch for another year; (2) push
it anyway and accept a reduction in pg_dump reliability.

I don't care for (2). I'd like to have lock strength reduction as
much as anybody, but it can't come at the price of reduction of
reliability.

I am sorry, but I think this is vastly overstating the scope of the
pg_dump problem. CREATE INDEX *already* doesn't require a AEL, and the
amount of problems that has caused in the past is surprisingly low. If
such a frequently used command doesn't cause problems, why are you
assuming other commands to be that problematic? And I think it's hard to
argue that the proposed changes are more likely to cause problems.

Let's try to go at this a bit more methodically. The commands that -
afaics - change their locklevel due to latest patch (v21) are:

[snip]

Good analysis. The hazards arise when pg_dump uses one of the ruleutils.c
deparse worker functions. As a cross-check to your study, I looked briefly at
the use of those functions in pg_dump and how this patch might affect them:

-- pg_get_constraintdef()

pg_dump reads the constraint OID with its transaction snapshot, so we will
never see a too-new constraint. Dropping a constraint still requires
AccessExclusiveLock.

Concerning VALIDATE CONSTRAINT, pg_dump reads convalidated with its
transaction snapshot and uses that to decide whether to dump the CHECK
constraint as part of the CREATE TABLE or as a separate ALTER TABLE ADD
CONSTRAINT following the data load. However, pg_get_constraintdef() reads the
latest convalidated to decide whether to emit NOT VALID. Consequently, one
can get a dump in which the dumped table data did not yet conform to the
constraint, and the ALTER TABLE ADD CONSTRAINT (w/o NOT VALID) fails.
(Suppose you deleted the last invalid rows just before executing the VALIDATE
CONSTRAINT. I tested this by committing the DELETE + VALIDATE CONSTRAINT with
pg_dump stopped at getTableAttrs().)

One hacky, but maintainable and effective, solution to the VALIDATE CONSTRAINT
problem is to have pg_dump tack on a NOT VALID if pg_get_constraintdef() did
not do so. It's, conveniently, the last part of the definition. I would tend
to choose this. We could also just decide this isn't bad enough to worry
about. The consequence is that an ALTER TABLE ADD CONSTRAINT fails. Assuming
no --single-transaction for the original restoral, you just add NOT VALID to
the command and rerun. Like most of the potential new pg_dump problems, this
can already happen today if the relevant database changes happen between
taking the pg_dump transaction snapshot and locking the tables.

-- pg_get_expr() for default expressions

pg_dump reads pg_attrdef.adbin using its transaction snapshot, so it will
never see a too-new default. This does allow us to read a dropped default.
That's not a problem directly. However, suppose the default references a
function dropped at the same time as the default. pg_dump could fail in
pg_get_expr().

-- pg_get_indexdef()

As you explained elsewhere, new indexes are no problem. DROP INDEX still
requires AccessExclusiveLock. Overall, no problems here.

-- pg_get_ruledef()

The patch changes lock requirements for enabling and disabling of rules, but
that is all separate from the rule expression handling. No problems.

-- pg_get_triggerdef()

The patch reduces CREATE TRIGGER and DROP TRIGGER to ShareUpdateExclusiveLock.
The implications for pg_dump are similar to those for pg_get_expr().

-- pg_get_viewdef()

Untamed: pg_dump does not lock views at all.

One thing not to forget is that you can always get the old mutual exclusion
back by issuing LOCK TABLE just before a DDL operation. If some unlucky user
regularly gets pg_dump failures due to concurrent DROP TRIGGER, he has a
workaround. There's no comparable way for someone who would not experience
that problem to weaken the now-hardcoded AccessExclusiveLock. Many
consequences of insufficient locking are too severe for that workaround to
bring comfort, but the pg_dump failure scenarios around pg_get_expr() and
pg_get_triggerdef() seem mild enough. Restore-time failures are more serious,
hence my recommendation to put a hack in pg_dump around VALIDATE CONSTRAINT.

Thanks,
nm

--
Noah Misch
EnterpriseDB http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#97Simon Riggs
simon@2ndQuadrant.com
In reply to: Noah Misch (#96)
Re: ALTER TABLE lock strength reduction patch is unsafe Reply-To:

On 6 March 2014 22:43, Noah Misch <noah@leadboat.com> wrote:

Good analysis. The hazards arise when pg_dump uses one of the ruleutils.c
deparse worker functions.

Ah, good. We're thinking along the same lines. I was already working
on this analysis also. I'll complete mine and then compare notes.

One thing not to forget is that you can always get the old mutual exclusion
back by issuing LOCK TABLE just before a DDL operation. If some unlucky user
regularly gets pg_dump failures due to concurrent DROP TRIGGER, he has a
workaround. There's no comparable way for someone who would not experience
that problem to weaken the now-hardcoded AccessExclusiveLock.

Good point.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#98Simon Riggs
simon@2ndQuadrant.com
In reply to: Noah Misch (#96)
Re: ALTER TABLE lock strength reduction patch is unsafe Reply-To:

On 6 March 2014 22:43, Noah Misch <noah@leadboat.com> wrote:

On Tue, Mar 04, 2014 at 06:50:17PM +0100, Andres Freund wrote:

On 2014-03-04 11:40:10 -0500, Tom Lane wrote:

Robert Haas <robertmhaas@gmail.com> writes: > I think this is all too
late for 9.4, though.

I agree with the feeling that a meaningful fix for pg_dump isn't going
to get done for 9.4. So that leaves us with the alternatives of (1)
put off the lock-strength-reduction patch for another year; (2) push
it anyway and accept a reduction in pg_dump reliability.

I don't care for (2). I'd like to have lock strength reduction as
much as anybody, but it can't come at the price of reduction of
reliability.

I am sorry, but I think this is vastly overstating the scope of the
pg_dump problem. CREATE INDEX *already* doesn't require a AEL, and the
amount of problems that has caused in the past is surprisingly low. If
such a frequently used command doesn't cause problems, why are you
assuming other commands to be that problematic? And I think it's hard to
argue that the proposed changes are more likely to cause problems.

Let's try to go at this a bit more methodically. The commands that -
afaics - change their locklevel due to latest patch (v21) are:

[snip]

Good analysis. The hazards arise when pg_dump uses one of the ruleutils.c
deparse worker functions. As a cross-check to your study, I looked briefly at
the use of those functions in pg_dump and how this patch might affect them:

-- pg_get_constraintdef()

pg_dump reads the constraint OID with its transaction snapshot, so we will
never see a too-new constraint. Dropping a constraint still requires
AccessExclusiveLock.

Agreed

Concerning VALIDATE CONSTRAINT, pg_dump reads convalidated with its
transaction snapshot and uses that to decide whether to dump the CHECK
constraint as part of the CREATE TABLE or as a separate ALTER TABLE ADD
CONSTRAINT following the data load. However, pg_get_constraintdef() reads the
latest convalidated to decide whether to emit NOT VALID. Consequently, one
can get a dump in which the dumped table data did not yet conform to the
constraint, and the ALTER TABLE ADD CONSTRAINT (w/o NOT VALID) fails.
(Suppose you deleted the last invalid rows just before executing the VALIDATE
CONSTRAINT. I tested this by committing the DELETE + VALIDATE CONSTRAINT with
pg_dump stopped at getTableAttrs().)

One hacky, but maintainable and effective, solution to the VALIDATE CONSTRAINT
problem is to have pg_dump tack on a NOT VALID if pg_get_constraintdef() did
not do so. It's, conveniently, the last part of the definition. I would tend
to choose this. We could also just decide this isn't bad enough to worry
about. The consequence is that an ALTER TABLE ADD CONSTRAINT fails. Assuming
no --single-transaction for the original restoral, you just add NOT VALID to
the command and rerun. Like most of the potential new pg_dump problems, this
can already happen today if the relevant database changes happen between
taking the pg_dump transaction snapshot and locking the tables.

Too hacky for me, but some good thinking. My proposed solution is below.

-- pg_get_expr() for default expressions

pg_dump reads pg_attrdef.adbin using its transaction snapshot, so it will
never see a too-new default. This does allow us to read a dropped default.
That's not a problem directly. However, suppose the default references a
function dropped at the same time as the default. pg_dump could fail in
pg_get_expr().

-- pg_get_indexdef()

As you explained elsewhere, new indexes are no problem. DROP INDEX still
requires AccessExclusiveLock. Overall, no problems here.

-- pg_get_ruledef()

The patch changes lock requirements for enabling and disabling of rules, but
that is all separate from the rule expression handling. No problems.

-- pg_get_triggerdef()

The patch reduces CREATE TRIGGER and DROP TRIGGER to ShareUpdateExclusiveLock.
The implications for pg_dump are similar to those for pg_get_expr().

These are certainly concerning. What surprises me the most is that
pg_dump has been so happy to randomly mix SQL using the transaction
snapshot with sys cache access code using a different snapshot. If
that was intention there is no documentation in code or in the docs to
explain that.

-- pg_get_viewdef()

Untamed: pg_dump does not lock views at all.

OMG, its really a wonder pg_dump works at all.

One thing not to forget is that you can always get the old mutual exclusion
back by issuing LOCK TABLE just before a DDL operation. If some unlucky user
regularly gets pg_dump failures due to concurrent DROP TRIGGER, he has a
workaround. There's no comparable way for someone who would not experience
that problem to weaken the now-hardcoded AccessExclusiveLock. Many
consequences of insufficient locking are too severe for that workaround to
bring comfort, but the pg_dump failure scenarios around pg_get_expr() and
pg_get_triggerdef() seem mild enough. Restore-time failures are more serious,
hence my recommendation to put a hack in pg_dump around VALIDATE CONSTRAINT.

The right thing to do here is to not push to the extremes. If we mess
too much with the ruleutil stuff it will just be buggy. A more
considered analysis in a later release is required for a full and
complete approach. As I indicated earlier, an 80/20 solution is better
for this release.

Slimming down the patch, I've removed changes to lock levels for
almost all variants. The only lock levels now reduced are those for
VALIDATE, plus setting of relation and attribute level options.

VALIDATE is implemented by calling pg_get_constraintdef_mvcc(), a
slightly modified variant of pg_get_constraintdef that uses the
transaction snapshot. I propose this rather than Noah's solution
solely because this will allow any user to request the MVCC data,
rather than implement a hack that only works for pg_dump. I will post
the patch later today.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#99Simon Riggs
simon@2ndQuadrant.com
In reply to: Simon Riggs (#98)
1 attachment(s)
Re: ALTER TABLE lock strength reduction patch is unsafe Reply-To:

On 7 March 2014 09:04, Simon Riggs <simon@2ndquadrant.com> wrote:

The right thing to do here is to not push to the extremes. If we mess
too much with the ruleutil stuff it will just be buggy. A more
considered analysis in a later release is required for a full and
complete approach. As I indicated earlier, an 80/20 solution is better
for this release.

Slimming down the patch, I've removed changes to lock levels for
almost all variants. The only lock levels now reduced are those for
VALIDATE, plus setting of relation and attribute level options.

VALIDATE is implemented by calling pg_get_constraintdef_mvcc(), a
slightly modified variant of pg_get_constraintdef that uses the
transaction snapshot. I propose this rather than Noah's solution
solely because this will allow any user to request the MVCC data,
rather than implement a hack that only works for pg_dump. I will post
the patch later today.

Implemented in attached patch, v22

The following commands (only) are allowed with
ShareUpdateExclusiveLock, patch includes doc changes.

ALTER TABLE ... VALIDATE CONSTRAINT constraint_name
covered by isolation test, plus verified manually with pg_dump

ALTER TABLE ... ALTER COLUMN ... SET STATISTICS
ALTER TABLE ... ALTER COLUMN ... SET (...)
ALTER TABLE ... ALTER COLUMN ... RESET (...)

ALTER TABLE ... CLUSTER ON ...
ALTER TABLE ... SET WITHOUT CLUSTER
ALTER TABLE ... SET (...)
covered by isolation test

ALTER TABLE ... RESET (...)

ALTER INDEX ... SET (...)
ALTER INDEX ... RESET (...)

All other ALTER commands take AccessExclusiveLock

I commend this patch to you for final review; I would like to commit
this in a few days.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

reduce_lock_levels.v22.patchapplication/octet-stream; name=reduce_lock_levels.v22.patchDownload
diff --git a/doc/src/sgml/mvcc.sgml b/doc/src/sgml/mvcc.sgml
index 2ca423c..7e6b4b3 100644
--- a/doc/src/sgml/mvcc.sgml
+++ b/doc/src/sgml/mvcc.sgml
@@ -865,7 +865,8 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         <para>
          Acquired by <command>VACUUM</command> (without <option>FULL</option>),
          <command>ANALYZE</>, <command>CREATE INDEX CONCURRENTLY</>, and
-         some forms of <command>ALTER TABLE</command>.
+         <command>ALTER TABLE VALIDATE</command> and other
+         <command>ALTER TABLE</command> variants that set options.
         </para>
        </listitem>
       </varlistentry>
@@ -951,7 +952,7 @@ ERROR:  could not serialize access due to read/write dependencies among transact
         </para>
 
         <para>
-         Acquired by the <command>ALTER TABLE</>, <command>DROP TABLE</>,
+         Acquired by the <command>ALTER TABLE</> for rewriting, <command>DROP TABLE</>,
          <command>TRUNCATE</command>, <command>REINDEX</command>,
          <command>CLUSTER</command>, and <command>VACUUM FULL</command>
          commands.
diff --git a/doc/src/sgml/ref/alter_index.sgml b/doc/src/sgml/ref/alter_index.sgml
index 94a7af0..a79caf2 100644
--- a/doc/src/sgml/ref/alter_index.sgml
+++ b/doc/src/sgml/ref/alter_index.sgml
@@ -33,7 +33,10 @@ ALTER INDEX [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> RESE
 
   <para>
    <command>ALTER INDEX</command> changes the definition of an existing index.
-   There are several subforms:
+   There are several subforms described below. Note that the lock level required
+   may differ for each subform. An <literal>ACCESS EXCLUSIVE</literal>
+   lock is held unless explicitly noted. When multiple subcommands are listed, the
+   lock held will be the strictest one required from any subcommand.
 
   <variablelist>
 
@@ -82,6 +85,10 @@ ALTER INDEX [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> RESE
       <xref linkend="SQL-REINDEX">
       to get the desired effects.
      </para>
+     <para>
+      Changing storage parameters requires only a
+      <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -93,6 +100,10 @@ ALTER INDEX [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> RESE
       their defaults.  As with <literal>SET</>, a <literal>REINDEX</literal>
       might be needed to update the index entirely.
      </para>
+     <para>
+      Changing storage parameters requires only a
+      <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 2b02e66..2d5aeb0 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -84,7 +84,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
 
   <para>
    <command>ALTER TABLE</command> changes the definition of an existing table.
-   There are several subforms:
+   There are several subforms described below. Note that the lock level required
+   may differ for each subform. An <literal>ACCESS EXCLUSIVE</literal> lock is held
+   unless explicitly noted. When multiple subcommands are listed, the lock
+   held will be the strictest one required from any subcommand.
 
   <variablelist>
    <varlistentry>
@@ -181,6 +184,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <productname>PostgreSQL</productname> query planner, refer to
       <xref linkend="planner-stats">.
      </para>
+     <para>
+      SET STATISTICS requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -213,6 +219,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       of statistics by the <productname>PostgreSQL</productname> query
       planner, refer to <xref linkend="planner-stats">.
      </para>
+     <para>
+      Changing per-attribute options uses a
+      <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -338,11 +348,17 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       Nothing happens if the constraint is already marked valid.
      </para>
      <para>
-      Validation can be a long process on larger tables and currently requires
-      an <literal>ACCESS EXCLUSIVE</literal> lock.  The value of separating
+      Validation can be a long process on larger tables. The value of separating
       validation from initial creation is that you can defer validation to less
       busy times, or can be used to give additional time to correct pre-existing
-      errors while preventing new errors.
+      errors while preventing new errors. Note also that validation on its own
+      does not prevent normal write commands against the table while it runs.
+     </para>
+     <para>
+      Validation requires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock
+      on the table being altered. If the constraint is a foreign key then
+      a <literal>ROW SHARE</literal> lock is also required on
+      the table referenced by the constraint.
      </para>
     </listitem>
    </varlistentry>
@@ -408,6 +424,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       <xref linkend="SQL-CLUSTER">
       operations.  It does not actually re-cluster the table.
      </para>
+     <para>
+      Changing cluster options uses a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -420,6 +439,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       index specification from the table.  This affects
       future cluster operations that don't specify an index.
      </para>
+     <para>
+      Changing cluster options uses a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -467,6 +489,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       FULL</>, <xref linkend="SQL-CLUSTER"> or one of the forms
       of <command>ALTER TABLE</> that forces a table rewrite.
      </para>
+     <para>
+      Changing storage parameters requires only a
+      <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
 
      <note>
       <para>
@@ -489,6 +515,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       defaults.  As with <literal>SET</>, a table rewrite might be
       needed to update the table entirely.
      </para>
+     <para>
+      Changing storage parameters requires only a
+      <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -1075,6 +1105,14 @@ ALTER TABLE distributors ADD CONSTRAINT distfk FOREIGN KEY (address) REFERENCES
   </para>
 
   <para>
+   To add a foreign key constraint to a table minimising impact on other work:
+<programlisting>
+ALTER TABLE distributors ADD CONSTRAINT distfk FOREIGN KEY (address) REFERENCES addresses (address) NOT VALID;
+ALTER TABLE distributors VALIDATE CONSTRAINT distfk;
+</programlisting>
+  </para>
+
+  <para>
    To add a (multicolumn) unique constraint to a table:
 <programlisting>
 ALTER TABLE distributors ADD CONSTRAINT dist_id_zipcode_key UNIQUE (dist_id, zipcode);
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index f044280..90f1794 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -27,6 +27,7 @@
 #include "catalog/toasting.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "storage/lock.h"
 #include "utils/builtins.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
@@ -35,7 +36,7 @@
 Oid			binary_upgrade_next_toast_pg_type_oid = InvalidOid;
 
 static bool create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
-				   Datum reloptions);
+				   Datum reloptions, LOCKMODE lockmode);
 static bool needs_toast_table(Relation rel);
 
 
@@ -52,7 +53,7 @@ static bool needs_toast_table(Relation rel);
  * to end with CommandCounterIncrement if it makes any changes.
  */
 void
-AlterTableCreateToastTable(Oid relOid, Datum reloptions)
+AlterTableCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode)
 {
 	Relation	rel;
 
@@ -63,10 +64,10 @@ AlterTableCreateToastTable(Oid relOid, Datum reloptions)
 	 * concurrent readers of the pg_class tuple won't have visibility issues,
 	 * so let's be safe.
 	 */
-	rel = heap_open(relOid, AccessExclusiveLock);
+	rel = heap_open(relOid, lockmode);
 
 	/* create_toast_table does all the work */
-	(void) create_toast_table(rel, InvalidOid, InvalidOid, reloptions);
+	(void) create_toast_table(rel, InvalidOid, InvalidOid, reloptions, lockmode);
 
 	heap_close(rel, NoLock);
 }
@@ -91,7 +92,8 @@ BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid)
 						relName)));
 
 	/* create_toast_table does all the work */
-	if (!create_toast_table(rel, toastOid, toastIndexOid, (Datum) 0))
+	if (!create_toast_table(rel, toastOid, toastIndexOid, (Datum) 0,
+							AccessExclusiveLock))
 		elog(ERROR, "\"%s\" does not require a toast table",
 			 relName);
 
@@ -107,7 +109,8 @@ BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid)
  * bootstrap they can be nonzero to specify hand-assigned OIDs
  */
 static bool
-create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, Datum reloptions)
+create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
+				   Datum reloptions, LOCKMODE lockmode)
 {
 	Oid			relOid = RelationGetRelid(rel);
 	HeapTuple	reltup;
@@ -161,6 +164,14 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, Datum reloptio
 		return false;
 
 	/*
+	 * We need to create a toast table. We shouldn't
+	 * have got this far if we're in ALTER TABLE unless
+	 * we are holding AccessExclusiveLock.
+	 */
+	if (lockmode < AccessExclusiveLock)
+		elog(ERROR, "AccessExclusiveLock required to add toast tables.");
+
+	/*
 	 * Create the toast table and its index
 	 */
 	snprintf(toast_relname, sizeof(toast_relname),
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 8b18e4a..bbe87ae 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -714,7 +714,11 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, bool forcetemp,
 		if (isNull)
 			reloptions = (Datum) 0;
 
-		AlterTableCreateToastTable(OIDNewHeap, reloptions);
+		/*
+		 * Adding a toast table means we must have an AccessExclusiveLock,
+		 * which we need to have acquired before we examine the table.
+		 */
+		AlterTableCreateToastTable(OIDNewHeap, reloptions, AccessExclusiveLock);
 
 		ReleaseSysCache(tuple);
 	}
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 73e6e20..ed38295 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -373,7 +373,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 
 	(void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true);
 
-	AlterTableCreateToastTable(intoRelationId, toast_options);
+	AlterTableCreateToastTable(intoRelationId, toast_options, AccessExclusiveLock);
 
 	/* Create the "view" part of a materialized view. */
 	if (is_matview)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1de3170..5a9a867 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2687,8 +2687,7 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
  * The caller must lock the relation, with an appropriate lock level
  * for the subcommands requested. Any subcommand that needs to rewrite
  * tuples in the table forces the whole command to be executed with
- * AccessExclusiveLock (actually, that is currently required always, but
- * we hope to relax it at some point).	We pass the lock level down
+ * AccessExclusiveLock.  We pass the lock level down
  * so that we can apply it recursively to inherited tables. Note that the
  * lock level we want as we recurse might well be higher than required for
  * that specific subcommand. So we pass down the overall lock requirement,
@@ -2731,6 +2730,29 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse)
 }
 
 /*
+ * AlterViewInternal
+ *
+ * ALTER VIEW with target specified by OID
+ *
+ * We do not reject if the relation is already open, because it's quite
+ * likely that one or more layers of caller have it open.  That means it
+ * is unsafe to use this entry point for alterations that could break
+ * existing query plans.
+ *
+ * All changes to views require AccessExclusiveLock
+ */
+void
+AlterViewInternal(Oid relid, List *cmds, bool recurse)
+{
+	Relation	rel;
+	LOCKMODE	lockmode = AccessExclusiveLock;
+
+	rel = relation_open(relid, lockmode);
+
+	ATController(rel, cmds, recurse, lockmode);
+}
+
+/*
  * AlterTableGetLockLevel
  *
  * Sets the overall lock level required for the supplied list of subcommands.
@@ -2750,35 +2772,29 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse)
  * ALTER TABLE RENAME which is treated as a different statement type T_RenameStmt
  * and does not travel through this section of code and cannot be combined with
  * any of the subcommands given here.
+ *
+ * Note that Hot Standby only knows about AccessExclusiveLocks on the master
+ * so any changes that might affect SELECTs running on standbys need to use
+ * AccessExclusiveLocks even if you think a lesser lock would do, unless you
+ * have a solution for that also.
+ *
+ * Also note that pg_dump uses only an AccessShareLock, meaning that anything
+ * that takes a lock less than AccessExclusiveLock can change object definitions
+ * while pg_dump is running. Be careful to check that the appropriate data is
+ * derived by pg_dump using an MVCC snapshot, rather than syscache lookups,
+ * otherwise we might end up with an inconsistent dump that can't restore.
+ *
+ * Be careful to ensure this function is called for Tables and Indexes only.
+ * It is not currently safe to be called for Views because security_barrier
+ * is listed as an option and so would be allowed to be set at a level lower
+ * than AccessExclusiveLock, which would not be correct.
  */
 LOCKMODE
 AlterTableGetLockLevel(List *cmds)
 {
 	/*
-	 * Late in 9.1 dev cycle a number of issues were uncovered with access to
-	 * catalog relations, leading to the decision to re-enforce all DDL at
-	 * AccessExclusiveLock level by default.
-	 *
-	 * The issues are that there is a pervasive assumption in the code that
-	 * the catalogs will not be read unless an AccessExclusiveLock is held. If
-	 * that rule is relaxed, we must protect against a number of potential
-	 * effects - infrequent, but proven possible with test cases where
-	 * multiple DDL operations occur in a stream against frequently accessed
-	 * tables.
-	 *
-	 * 1. Catalog tables were read using SnapshotNow, which has a race bug that
-	 * allows a scan to return no valid rows even when one is present in the
-	 * case of a commit of a concurrent update of the catalog table.
-	 * SnapshotNow also ignores transactions in progress, so takes the latest
-	 * committed version without waiting for the latest changes.
-	 *
-	 * 2. Relcache needs to be internally consistent, so unless we lock the
-	 * definition during reads we have no way to guarantee that.
-	 *
-	 * 3. Catcache access isn't coordinated at all so refreshes can occur at
-	 * any time.
+	 * This only works if we read catalog tables using MVCC snapshots.
 	 */
-#ifdef REDUCED_ALTER_TABLE_LOCK_LEVELS
 	ListCell   *lcmd;
 	LOCKMODE	lockmode = ShareUpdateExclusiveLock;
 
@@ -2790,28 +2806,55 @@ AlterTableGetLockLevel(List *cmds)
 		switch (cmd->subtype)
 		{
 				/*
-				 * Need AccessExclusiveLock for these subcommands because they
-				 * affect or potentially affect both read and write
-				 * operations.
-				 *
-				 * New subcommand types should be added here by default.
+				 * These subcommands rewrite the heap, so require full locks.
 				 */
 			case AT_AddColumn:	/* may rewrite heap, in some cases and visible
 								 * to SELECT */
-			case AT_DropColumn:	/* change visible to SELECT */
-			case AT_AddColumnToView:	/* CREATE VIEW */
+			case AT_SetTableSpace:		/* must rewrite heap */
 			case AT_AlterColumnType:	/* must rewrite heap */
+			case AT_AddOids:			/* must rewrite heap */
+				cmd_lockmode = AccessExclusiveLock;
+				break;
+
+				/*
+				 * These subcommands may require addition of toast tables.
+				 */
+			case AT_SetStorage:			/* may add toast tables, see ATRewriteCatalogs() */
+				cmd_lockmode = AccessExclusiveLock;
+				break;
+
+				/*
+				 * Removing constraints can affect SELECTs that have been
+				 * optimised assuming the constraint holds true.
+				 */
 			case AT_DropConstraint:		/* as DROP INDEX */
-			case AT_AddOids:	/* must rewrite heap */
-			case AT_DropOids:	/* calls AT_DropColumn */
+			case AT_DropNotNull:		/* may change some SQL plans */
+				cmd_lockmode = AccessExclusiveLock;
+				break;
+
+				/*
+				 * Subcommands that may be visible to concurrent SELECTs
+				 */
+			case AT_DropColumn:			/* change visible to SELECT */
+			case AT_AddColumnToView:	/* CREATE VIEW */
+			case AT_DropOids:			/* calls AT_DropColumn */
 			case AT_EnableAlwaysRule:	/* may change SELECT rules */
 			case AT_EnableReplicaRule:	/* may change SELECT rules */
-			case AT_EnableRule:	/* may change SELECT rules */
+			case AT_EnableRule:			/* may change SELECT rules */
 			case AT_DisableRule:		/* may change SELECT rules */
+				cmd_lockmode = AccessExclusiveLock;
+				break;
+
+				/*
+				 * Changing owner may remove implicit SELECT privileges
+				 */
 			case AT_ChangeOwner:		/* change visible to SELECT */
-			case AT_SetTableSpace:		/* must rewrite heap */
-			case AT_DropNotNull:		/* may change some SQL plans */
-			case AT_SetNotNull:
+				cmd_lockmode = AccessExclusiveLock;
+				break;
+
+				/*
+				 * Changing foreign table options may affect optimisation.
+				 */
 			case AT_GenericOptions:
 			case AT_AlterColumnGenericOptions:
 				cmd_lockmode = AccessExclusiveLock;
@@ -2819,6 +2862,7 @@ AlterTableGetLockLevel(List *cmds)
 
 				/*
 				 * These subcommands affect write operations only.
+				 * XXX Theoretically, these could be ShareRowExclusiveLock.
 				 */
 			case AT_ColumnDefault:
 			case AT_ProcessedConstraint:		/* becomes AT_AddConstraint */
@@ -2832,10 +2876,12 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_DisableTrig:
 			case AT_DisableTrigAll:
 			case AT_DisableTrigUser:
+			case AT_AlterConstraint:
 			case AT_AddIndex:	/* from ADD CONSTRAINT */
 			case AT_AddIndexConstraint:
 			case AT_ReplicaIdentity:
-				cmd_lockmode = ShareRowExclusiveLock;
+			case AT_SetNotNull:
+				cmd_lockmode = AccessExclusiveLock;
 				break;
 
 			case AT_AddConstraint:
@@ -2854,8 +2900,10 @@ AlterTableGetLockLevel(List *cmds)
 							 * could reduce the lock strength to ShareLock if
 							 * we can work out how to allow concurrent catalog
 							 * updates.
+							 * XXX Might be set down to ShareRowExclusiveLock
+							 * but requires further analysis.
 							 */
-							cmd_lockmode = ShareRowExclusiveLock;
+							cmd_lockmode = AccessExclusiveLock;
 							break;
 						case CONSTR_FOREIGN:
 
@@ -2863,12 +2911,15 @@ AlterTableGetLockLevel(List *cmds)
 							 * We add triggers to both tables when we add a
 							 * Foreign Key, so the lock level must be at least
 							 * as strong as CREATE TRIGGER.
+							 * XXX Might be set down to ShareRowExclusiveLock
+							 * though trigger info is accessed by
+							 * pg_get_triggerdef
 							 */
-							cmd_lockmode = ShareRowExclusiveLock;
+							cmd_lockmode = AccessExclusiveLock;
 							break;
 
 						default:
-							cmd_lockmode = ShareRowExclusiveLock;
+							cmd_lockmode = AccessExclusiveLock;
 					}
 				}
 				break;
@@ -2879,22 +2930,23 @@ AlterTableGetLockLevel(List *cmds)
 				 * behaviour, while queries started after we commit will see
 				 * new behaviour. No need to prevent reads or writes to the
 				 * subtable while we hook it up though.
+				 * Changing the TupDesc may be a problem, so keep highest lock.
 				 */
 			case AT_AddInherit:
 			case AT_DropInherit:
-				cmd_lockmode = ShareUpdateExclusiveLock;
+				cmd_lockmode = AccessExclusiveLock;
 				break;
 
 				/*
 				 * These subcommands affect implicit row type conversion. They
-				 * have affects similar to CREATE/DROP CAST on queries.  We
+				 * have affects similar to CREATE/DROP CAST on queries.
 				 * don't provide for invalidating parse trees as a result of
-				 * such changes.  Do avoid concurrent pg_class updates,
-				 * though.
+				 * such changes, so we keep these at AccessExclusiveLock.
 				 */
 			case AT_AddOf:
 			case AT_DropOf:
-				cmd_lockmode = ShareUpdateExclusiveLock;
+				cmd_lockmode = AccessExclusiveLock;
+				break;
 
 				/*
 				 * These subcommands affect general strategies for performance
@@ -2906,17 +2958,15 @@ AlterTableGetLockLevel(List *cmds)
 				 * applies: we don't currently allow concurrent catalog
 				 * updates.
 				 */
-			case AT_SetStatistics:
-			case AT_ClusterOn:
-			case AT_DropCluster:
-			case AT_SetRelOptions:
-			case AT_ResetRelOptions:
-			case AT_ReplaceRelOptions:
-			case AT_SetOptions:
-			case AT_ResetOptions:
-			case AT_SetStorage:
-			case AT_AlterConstraint:
-			case AT_ValidateConstraint:
+			case AT_SetStatistics:		/* Uses MVCC in getTableAttrs() */
+			case AT_ClusterOn:			/* Uses MVCC in getIndexes() */
+			case AT_DropCluster:		/* Uses MVCC in getIndexes() */
+			case AT_SetRelOptions:		/* Uses MVCC in getIndexes() and getTables() */
+			case AT_ResetRelOptions:	/* Uses MVCC in getIndexes() and getTables() */
+			case AT_ReplaceRelOptions:	/* Uses MVCC in getIndexes() and getTables() */
+			case AT_SetOptions:			/* Uses MVCC in getTableAttrs() */
+			case AT_ResetOptions:		/* Uses MVCC in getTableAttrs() */
+			case AT_ValidateConstraint:	/* Uses MVCC in getConstraints() */
 				cmd_lockmode = ShareUpdateExclusiveLock;
 				break;
 
@@ -2932,9 +2982,6 @@ AlterTableGetLockLevel(List *cmds)
 		if (cmd_lockmode > lockmode)
 			lockmode = cmd_lockmode;
 	}
-#else
-	LOCKMODE	lockmode = AccessExclusiveLock;
-#endif
 
 	return lockmode;
 }
@@ -3269,7 +3316,7 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
 
 		if (tab->relkind == RELKIND_RELATION ||
 			tab->relkind == RELKIND_MATVIEW)
-			AlterTableCreateToastTable(tab->relid, (Datum) 0);
+			AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
 	}
 }
 
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index fa74bd2..7c46293 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -500,8 +500,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * can skip this for internally generated triggers, since the name
 	 * modification above should be sufficient.
 	 *
-	 * NOTE that this is cool only because we have AccessExclusiveLock on the
-	 * relation, so the trigger set won't be changing underneath us.
+	 * NOTE that this is cool only because we have a sufficient lock on the
+	 * relation to ensure the trigger set won't be changing underneath us.
 	 */
 	if (!isInternal)
 	{
@@ -1243,8 +1243,8 @@ renametrig(RenameStmt *stmt)
 	 * on tgrelid/tgname would complain anyway) and to ensure a trigger does
 	 * exist with oldname.
 	 *
-	 * NOTE that this is cool only because we have AccessExclusiveLock on the
-	 * relation, so the trigger set won't be changing underneath us.
+	 * NOTE that this is cool only because we have a sufficient lock on the
+	 * relation to ensure that the trigger set won't be changing underneath us.
 	 */
 	tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
 
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 1735762..13ab1a2 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -206,7 +206,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
 		}
 
 		/* OK, let's do it. */
-		AlterTableInternal(viewOid, atcmds, true);
+		AlterViewInternal(viewOid, atcmds, true);
 
 		/*
 		 * Seems okay, so return the OID of the pre-existing view.
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index d1621ad..b5b8689 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -927,7 +927,8 @@ ProcessUtilitySlow(Node *parsetree,
 												   toast_options,
 												   true);
 
-							AlterTableCreateToastTable(relOid, toast_options);
+							AlterTableCreateToastTable(relOid, toast_options,
+													   AccessExclusiveLock);
 						}
 						else if (IsA(stmt, CreateForeignTableStmt))
 						{
@@ -962,15 +963,17 @@ ProcessUtilitySlow(Node *parsetree,
 					Oid			relid;
 					List	   *stmts;
 					ListCell   *l;
-					LOCKMODE	lockmode;
+					LOCKMODE	lockmode = AccessExclusiveLock;
 
 					/*
 					 * Figure out lock mode, and acquire lock.	This also does
 					 * basic permissions checks, so that we won't wait for a
 					 * lock on (for example) a relation on which we have no
-					 * permissions.
+					 * permissions. Only safe for Tables and Indexes currently.
 					 */
-					lockmode = AlterTableGetLockLevel(atstmt->cmds);
+					if (atstmt->relkind == OBJECT_TABLE ||
+						atstmt->relkind == OBJECT_INDEX)
+						lockmode = AlterTableGetLockLevel(atstmt->cmds);
 					relid = AlterTableLookupRelation(atstmt, lockmode);
 
 					if (OidIsValid(relid))
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index add5cd1..038bc5a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -54,6 +54,7 @@
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
 #include "utils/typcache.h"
@@ -289,7 +290,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
-							int prettyFlags);
+							int prettyFlags, bool useSyscache);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
 				   int prettyFlags);
 static int print_function_arguments(StringInfo buf, HeapTuple proctup,
@@ -1258,10 +1259,30 @@ pg_get_constraintdef(PG_FUNCTION_ARGS)
 	Oid			constraintId = PG_GETARG_OID(0);
 	int			prettyFlags;
 
+	/*
+	 * Get the definition of the constraint as it is right now
+	 */
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_constraintdef_worker(constraintId,
+																false,
+															  prettyFlags,
+															  true)));
+}
+
+Datum
+pg_get_constraintdef_mvcc(PG_FUNCTION_ARGS)
+{
+	Oid			constraintId = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	/*
+	 * Get the definition of the constraint as of the transaction snapshot.
+	 */
 	prettyFlags = PRETTYFLAG_INDENT;
 	PG_RETURN_TEXT_P(string_to_text(pg_get_constraintdef_worker(constraintId,
 																false,
-															  prettyFlags)));
+															  prettyFlags,
+															  false)));
 }
 
 Datum
@@ -1274,27 +1295,57 @@ pg_get_constraintdef_ext(PG_FUNCTION_ARGS)
 	prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
 	PG_RETURN_TEXT_P(string_to_text(pg_get_constraintdef_worker(constraintId,
 																false,
-															  prettyFlags)));
+															  prettyFlags,
+															  true)));
 }
 
 /* Internal version that returns a palloc'd C string; no pretty-printing */
 char *
 pg_get_constraintdef_string(Oid constraintId)
 {
-	return pg_get_constraintdef_worker(constraintId, true, 0);
+	return pg_get_constraintdef_worker(constraintId, true, 0, true);
 }
 
 static char *
 pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
-							int prettyFlags)
+							int prettyFlags, bool useSyscache)
 {
 	HeapTuple	tup;
 	Form_pg_constraint conForm;
 	StringInfoData buf;
+	SysScanDesc	scandesc;
+	ScanKeyData scankey[1];
+	Relation	relation;
+
+	if (useSyscache)
+		tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constraintId));
+	else
+	{
+		Snapshot    snapshot = RegisterSnapshot(GetTransactionSnapshot());
+		PushActiveSnapshot(snapshot);
+
+		relation = heap_open(ConstraintRelationId, AccessShareLock);
+
+		ScanKeyInit(&scankey[0],
+					ObjectIdAttributeNumber,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(constraintId));
+
+		scandesc = systable_beginscan(relation,
+										ConstraintOidIndexId,
+										true,
+										snapshot,
+										1,
+										scankey);
+		tup = systable_getnext(scandesc);
+
+		PopActiveSnapshot();
+		UnregisterSnapshot(snapshot);
+	}
 
-	tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constraintId));
 	if (!HeapTupleIsValid(tup)) /* should not happen */
 		elog(ERROR, "cache lookup failed for constraint %u", constraintId);
+
 	conForm = (Form_pg_constraint) GETSTRUCT(tup);
 
 	initStringInfo(&buf);
@@ -1575,7 +1626,13 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 		appendStringInfoString(&buf, " NOT VALID");
 
 	/* Cleanup */
-	ReleaseSysCache(tup);
+	if (useSyscache)
+		ReleaseSysCache(tup);
+	else
+	{
+		systable_endscan(scandesc);
+		heap_close(relation, AccessShareLock);
+	}
 
 	return buf.data;
 }
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2810b35..086d9f8 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -161,6 +161,14 @@ static bool eoxact_list_overflowed = false;
 			eoxact_list_overflowed = true; \
 	} while (0)
 
+/*
+ * EOXactTupleDescArray stores *TupleDescs that (might) need AtEOXact
+ * cleanup work.  The array expands as needed; there is no hashtable because
+ * we don't need to access individual items except at EOXact.
+ */
+static TupleDesc *EOXactTupleDescArray;
+static int NextEOXactTupleDescNum = 0;
+static int EOXactTupleDescArrayLen = 0;
 
 /*
  *		macros to manipulate the lookup hashtables
@@ -219,11 +227,12 @@ static HTAB *OpClassCache = NULL;
 
 /* non-export function prototypes */
 
-static void RelationDestroyRelation(Relation relation);
+static void RelationDestroyRelation(Relation relation, bool remember_tupdesc);
 static void RelationClearRelation(Relation relation, bool rebuild);
 
 static void RelationReloadIndexInfo(Relation relation);
 static void RelationFlushRelation(Relation relation);
+static void RememberToFreeTupleDescAtEOX(TupleDesc td);
 static void AtEOXact_cleanup(Relation relation, bool isCommit);
 static void AtEOSubXact_cleanup(Relation relation, bool isCommit,
 					SubTransactionId mySubid, SubTransactionId parentSubid);
@@ -1811,7 +1820,7 @@ RelationReloadIndexInfo(Relation relation)
  *	Caller must already have unhooked the entry from the hash table.
  */
 static void
-RelationDestroyRelation(Relation relation)
+RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 {
 	Assert(RelationHasReferenceCountZero(relation));
 
@@ -1831,7 +1840,20 @@ RelationDestroyRelation(Relation relation)
 	/* can't use DecrTupleDescRefCount here */
 	Assert(relation->rd_att->tdrefcount > 0);
 	if (--relation->rd_att->tdrefcount == 0)
-		FreeTupleDesc(relation->rd_att);
+	{
+		/*
+		 * If we Rebuilt a relcache entry during a transaction then its
+		 * possible we did that because the TupDesc changed as the result
+		 * of an ALTER TABLE that ran at less than AccessExclusiveLock.
+		 * It's possible someone copied that TupDesc, in which case the
+		 * copy would point to free'd memory. So if we rebuild an entry
+		 * we keep the TupDesc around until end of transaction, to be safe.
+		 */
+		if (remember_tupdesc)
+			RememberToFreeTupleDescAtEOX(relation->rd_att);
+		else
+			FreeTupleDesc(relation->rd_att);
+	}
 	list_free(relation->rd_indexlist);
 	bms_free(relation->rd_indexattr);
 	FreeTriggerDesc(relation->trigdesc);
@@ -1945,7 +1967,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 		RelationCacheDelete(relation);
 
 		/* And release storage */
-		RelationDestroyRelation(relation);
+		RelationDestroyRelation(relation, false);
 	}
 	else if (!IsTransactionState())
 	{
@@ -2012,7 +2034,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 		{
 			/* Should only get here if relation was deleted */
 			RelationCacheDelete(relation);
-			RelationDestroyRelation(relation);
+			RelationDestroyRelation(relation, false);
 			elog(ERROR, "relation %u deleted while still in use", save_relid);
 		}
 
@@ -2074,7 +2096,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 #undef SWAPFIELD
 
 		/* And now we can throw away the temporary entry */
-		RelationDestroyRelation(newrel);
+		RelationDestroyRelation(newrel, !keep_tupdesc);
 	}
 }
 
@@ -2312,6 +2334,37 @@ RelationCloseSmgrByOid(Oid relationId)
 	RelationCloseSmgr(relation);
 }
 
+void
+RememberToFreeTupleDescAtEOX(TupleDesc td)
+{
+	if (EOXactTupleDescArray == NULL)
+	{
+		MemoryContext	oldcxt;
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+		EOXactTupleDescArray = (TupleDesc *) palloc(16 * sizeof(TupleDesc));
+		EOXactTupleDescArrayLen = 16;
+		NextEOXactTupleDescNum = 0;
+		MemoryContextSwitchTo(oldcxt);
+	}
+	else if (NextEOXactTupleDescNum >= EOXactTupleDescArrayLen)
+	{
+		MemoryContext	oldcxt;
+		int32       	newlen = EOXactTupleDescArrayLen * 2;
+
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+		Assert(EOXactTupleDescArrayLen > 0);
+
+		EOXactTupleDescArray = (TupleDesc *) repalloc(EOXactTupleDescArray,
+														newlen * sizeof(TupleDesc));
+		EOXactTupleDescArrayLen = newlen;
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	EOXactTupleDescArray[NextEOXactTupleDescNum++] = td;
+}
+
 /*
  * AtEOXact_RelationCache
  *
@@ -2367,9 +2420,20 @@ AtEOXact_RelationCache(bool isCommit)
 		}
 	}
 
-	/* Now we're out of the transaction and can clear the list */
+	if (EOXactTupleDescArrayLen > 0)
+	{
+		Assert(EOXactTupleDescArray != NULL);
+		for (i = 0; i < NextEOXactTupleDescNum; i++)
+			FreeTupleDesc(EOXactTupleDescArray[i]);
+		pfree(EOXactTupleDescArray);
+		EOXactTupleDescArray = NULL;
+	}
+
+	/* Now we're out of the transaction and can clear the lists */
 	eoxact_list_len = 0;
 	eoxact_list_overflowed = false;
+	NextEOXactTupleDescNum = 0;
+	EOXactTupleDescArrayLen = 0;
 }
 
 /*
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4fabc1d..1448a5a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5335,9 +5335,14 @@ getConstraints(Archive *fout, TableInfo tblinfo[], int numTables)
 		selectSourceSchema(fout, tbinfo->dobj.namespace->dobj.name);
 
 		resetPQExpBuffer(query);
+
+		/*
+		 * Use MVCC to access constraintdef so that we get a definition
+		 * that matches the data we will select.
+		 */
 		appendPQExpBuffer(query,
 						  "SELECT tableoid, oid, conname, confrelid, "
-						  "pg_catalog.pg_get_constraintdef(oid) AS condef "
+						  "pg_catalog.pg_get_constraintdef_mvcc(oid) AS condef "
 						  "FROM pg_catalog.pg_constraint "
 						  "WHERE conrelid = '%u'::pg_catalog.oid "
 						  "AND contype = 'f'",
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index e33670c..0c989c6 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1963,6 +1963,8 @@ DATA(insert OID = 1662 (  pg_get_triggerdef    PGNSP PGUID 12 1 0 0 0 f f f f t
 DESCR("trigger description");
 DATA(insert OID = 1387 (  pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 25 "26" _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
 DESCR("constraint description");
+DATA(insert OID = 4033 (  pg_get_constraintdef_mvcc PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 25 "26" _null_ _null_ _null_ _null_ pg_get_constraintdef_mvcc _null_ _null_ _null_ ));
+DESCR("constraint description (mvcc)");
 DATA(insert OID = 1716 (  pg_get_expr		   PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 25 "194 26" _null_ _null_ _null_ _null_ pg_get_expr _null_ _null_ _null_ ));
 DESCR("deparse an encoded expression");
 DATA(insert OID = 1665 (  pg_get_serial_sequence	PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 25 "25 25" _null_ _null_ _null_ _null_ pg_get_serial_sequence _null_ _null_ _null_ ));
diff --git a/src/include/catalog/toasting.h b/src/include/catalog/toasting.h
index ca047f0..07b2169 100644
--- a/src/include/catalog/toasting.h
+++ b/src/include/catalog/toasting.h
@@ -14,10 +14,13 @@
 #ifndef TOASTING_H
 #define TOASTING_H
 
+#include "storage/lock.h"
+
 /*
  * toasting.c prototypes
  */
-extern void AlterTableCreateToastTable(Oid relOid, Datum reloptions);
+extern void AlterTableCreateToastTable(Oid relOid, Datum reloptions,
+									   LOCKMODE lockmode);
 extern void BootstrapToastTable(char *relName,
 					Oid toastOid, Oid toastIndexOid);
 
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 5c0518c..8a5185d 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -34,6 +34,7 @@ extern LOCKMODE AlterTableGetLockLevel(List *cmds);
 extern void ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lockmode);
 
 extern void AlterTableInternal(Oid relid, List *cmds, bool recurse);
+extern void AlterViewInternal(Oid relid, List *cmds, bool recurse);
 
 extern Oid	AlterTableNamespace(AlterObjectSchemaStmt *stmt);
 
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index b90d88d..43bf7d0 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -670,6 +670,7 @@ extern char *pg_get_indexdef_columns(Oid indexrelid, bool pretty);
 extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_constraintdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_constraintdef_mvcc(PG_FUNCTION_ARGS);
 extern Datum pg_get_constraintdef_ext(PG_FUNCTION_ARGS);
 extern char *pg_get_constraintdef_string(Oid constraintId);
 extern Datum pg_get_expr(PG_FUNCTION_ARGS);
diff --git a/src/test/isolation/expected/alter-table-1.out b/src/test/isolation/expected/alter-table-1.out
new file mode 100644
index 0000000..edf7288
--- /dev/null
+++ b/src/test/isolation/expected/alter-table-1.out
@@ -0,0 +1,2936 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1 at1 sc1 s2 at2 sc2 rx1 wx rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 at2 rx1 sc2 wx rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 at2 rx1 wx sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 at2 rx1 wx rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 at2 rx1 wx rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 at2 sc2 wx rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 at2 wx sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 at2 wx rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 at2 wx rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx at2 sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx at2 rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx at2 rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx rx1 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx rx1 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 s2 rx1 wx rx1 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 at2 sc2 wx rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 at2 wx sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 at2 wx rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 at2 wx rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx at2 sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx at2 rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx at2 rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx rx1 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx rx1 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 s2 wx rx1 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 at2 sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 at2 rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 at2 rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 rx1 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 rx1 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx s2 rx1 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx rx1 s2 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx rx1 s2 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx rx1 s2 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 sc1 rx1 wx rx1 c2 s2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 at2 sc2 wx rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+step sc1: COMMIT;
+step rx1: <... completed>
+a_id           
+
+1              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 at2 wx sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+step sc1: COMMIT;
+step rx1: <... completed>
+a_id           
+
+1              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 at2 wx rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+step sc1: COMMIT;
+step rx1: <... completed>
+a_id           
+
+1              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 at2 wx rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+step sc1: COMMIT;
+step rx1: <... completed>
+a_id           
+
+1              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx at2 sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+step sc1: COMMIT;
+step rx1: <... completed>
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx at2 rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+step sc1: COMMIT;
+step rx1: <... completed>
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx at2 rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+step sc1: COMMIT;
+step rx1: <... completed>
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx rx1 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+step sc1: COMMIT;
+step rx1: <... completed>
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx rx1 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+step sc1: COMMIT;
+step rx1: <... completed>
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 s2 wx rx1 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+step sc1: COMMIT;
+step rx1: <... completed>
+a_id           
+
+1              
+step s2: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 at2 sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+step sc1: COMMIT;
+step rx1: <... completed>
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 at2 rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+step sc1: COMMIT;
+step rx1: <... completed>
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 at2 rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+step sc1: COMMIT;
+step rx1: <... completed>
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 rx1 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+step sc1: COMMIT;
+step rx1: <... completed>
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 rx1 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+step sc1: COMMIT;
+step rx1: <... completed>
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx s2 rx1 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+step sc1: COMMIT;
+step rx1: <... completed>
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s2: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx rx1 s2 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+step sc1: COMMIT;
+step rx1: <... completed>
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx rx1 s2 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+step sc1: COMMIT;
+step rx1: <... completed>
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step c2: COMMIT;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx rx1 s2 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+step sc1: COMMIT;
+step rx1: <... completed>
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s2: BEGIN;
+step c2: COMMIT;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 sc1 wx rx1 c2 s2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+step sc1: COMMIT;
+step rx1: <... completed>
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx sc1 s2 at2 sc2 rx1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 at1 rx1 wx sc1 s2 at2 rx1 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 at1 rx1 wx sc1 s2 at2 rx1 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 at1 rx1 wx sc1 s2 rx1 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 at1 rx1 wx sc1 s2 rx1 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 at1 rx1 wx sc1 s2 rx1 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 at1 rx1 wx sc1 rx1 s2 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 at1 rx1 wx sc1 rx1 s2 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 at1 rx1 wx sc1 rx1 s2 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 at1 rx1 wx sc1 rx1 c2 s2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 at1 rx1 wx rx1 sc1 s2 at2 sc2 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 at1 rx1 wx rx1 sc1 s2 at2 c2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 at1 rx1 wx rx1 sc1 s2 c2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 at1 rx1 wx rx1 sc1 c2 s2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 at1 rx1 wx rx1 c2 sc1 s2 at2 sc2
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 sc1 s2 at2 sc2 wx rx1 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 sc1 s2 at2 wx sc2 rx1 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 sc1 s2 at2 wx rx1 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 sc1 s2 at2 wx rx1 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 sc1 s2 wx at2 sc2 rx1 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 sc1 s2 wx at2 rx1 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 sc1 s2 wx at2 rx1 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 sc1 s2 wx rx1 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 sc1 s2 wx rx1 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 sc1 s2 wx rx1 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 sc1 wx s2 at2 sc2 rx1 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 sc1 wx s2 at2 rx1 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 sc1 wx s2 at2 rx1 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 sc1 wx s2 rx1 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 sc1 wx s2 rx1 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 sc1 wx s2 rx1 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 sc1 wx rx1 s2 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 sc1 wx rx1 s2 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 sc1 wx rx1 s2 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 sc1 wx rx1 c2 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx sc1 s2 at2 sc2 rx1 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx sc1 s2 at2 rx1 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx sc1 s2 at2 rx1 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx sc1 s2 rx1 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx sc1 s2 rx1 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx sc1 s2 rx1 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx sc1 rx1 s2 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx sc1 rx1 s2 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx sc1 rx1 s2 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx sc1 rx1 c2 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx rx1 sc1 s2 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx rx1 sc1 s2 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx rx1 sc1 s2 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx rx1 sc1 c2 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: s1 rx1 at1 wx rx1 c2 sc1 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 wx at1 sc1 s2 at2 sc2 rx1 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 s2 at2 rx1 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 s2 at2 rx1 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 s2 rx1 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 s2 rx1 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 s2 rx1 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 rx1 s2 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 rx1 s2 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 rx1 s2 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 sc1 rx1 c2 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 rx1 sc1 s2 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 rx1 sc1 s2 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 rx1 sc1 s2 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 rx1 sc1 c2 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: s1 rx1 wx at1 rx1 c2 sc1 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 wx rx1 at1 sc1 s2 at2 sc2 c2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx rx1 at1 sc1 s2 at2 c2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx rx1 at1 sc1 s2 c2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx rx1 at1 sc1 c2 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: s1 rx1 wx rx1 at1 c2 sc1 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: s1 rx1 wx rx1 c2 at1 sc1 s2 at2 sc2
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 at1 sc1 s2 at2 sc2 wx rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 sc1 s2 at2 wx sc2 rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 sc1 s2 at2 wx rx1 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 sc1 s2 at2 wx rx1 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 sc1 s2 wx at2 sc2 rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 sc1 s2 wx at2 rx1 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 sc1 s2 wx at2 rx1 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 sc1 s2 wx rx1 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 sc1 s2 wx rx1 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 sc1 s2 wx rx1 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 sc1 wx s2 at2 sc2 rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 sc1 wx s2 at2 rx1 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 sc1 wx s2 at2 rx1 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 sc1 wx s2 rx1 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 sc1 wx s2 rx1 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 sc1 wx s2 rx1 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 sc1 wx rx1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 sc1 wx rx1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 sc1 wx rx1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 sc1 wx rx1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx sc1 s2 at2 sc2 rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx sc1 s2 at2 rx1 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx sc1 s2 at2 rx1 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx sc1 s2 rx1 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx sc1 s2 rx1 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx sc1 s2 rx1 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx sc1 rx1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx sc1 rx1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx sc1 rx1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx sc1 rx1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx rx1 sc1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx rx1 sc1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx rx1 sc1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx rx1 sc1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 s1 at1 wx rx1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 wx at1 sc1 s2 at2 sc2 rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 s2 at2 rx1 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 s2 at2 rx1 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 s2 rx1 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 s2 rx1 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 s2 rx1 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 rx1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 rx1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 rx1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 sc1 rx1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 rx1 sc1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 rx1 sc1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 rx1 sc1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 rx1 sc1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 s1 wx at1 rx1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 wx rx1 at1 sc1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx rx1 at1 sc1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx rx1 at1 sc1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx rx1 at1 sc1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 s1 wx rx1 at1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 s1 wx rx1 c2 at1 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step s1: BEGIN;
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx s1 at1 sc1 s2 at2 sc2 rx1 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 s2 at2 rx1 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 s2 at2 rx1 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 s2 rx1 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 s2 rx1 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 s2 rx1 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 rx1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 rx1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 rx1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 sc1 rx1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 rx1 sc1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 rx1 sc1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 rx1 sc1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 rx1 sc1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+invalid permutation detected
+
+starting permutation: rx1 wx s1 at1 rx1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx s1 rx1 at1 sc1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 rx1 at1 sc1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 rx1 at1 sc1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 rx1 at1 sc1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx s1 rx1 at1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx s1 rx1 c2 at1 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step s1: BEGIN;
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx rx1 s1 at1 sc1 s2 at2 sc2 c2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx rx1 s1 at1 sc1 s2 at2 c2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx rx1 s1 at1 sc1 s2 c2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx rx1 s1 at1 sc1 c2 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+invalid permutation detected
+
+starting permutation: rx1 wx rx1 s1 at1 c2 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; <waiting ...>
+step c2: COMMIT;
+step at1: <... completed>
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx rx1 s1 c2 at1 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step s1: BEGIN;
+step c2: COMMIT;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
+
+starting permutation: rx1 wx rx1 c2 s1 at1 sc1 s2 at2 sc2
+step rx1: SELECT * FROM b WHERE a_id = 1 LIMIT 1;
+a_id           
+
+1              
+step wx: INSERT INTO b VALUES (0);
+step rx1: SELECT * FROM b WHERE a_id = 3 LIMIT 3;
+a_id           
+
+3              
+3              
+3              
+step c2: COMMIT;
+step s1: BEGIN;
+step at1: ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID;
+step sc1: COMMIT;
+step s2: BEGIN;
+step at2: ALTER TABLE b VALIDATE CONSTRAINT bfk;
+step sc2: COMMIT;
diff --git a/src/test/isolation/expected/alter-table-2.out b/src/test/isolation/expected/alter-table-2.out
new file mode 100644
index 0000000..9e957bf
--- /dev/null
+++ b/src/test/isolation/expected/alter-table-2.out
@@ -0,0 +1,1681 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1 at1 c1 rx1 wx1 rx2 wx2 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+442368         
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+811008         
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 c1 wx1 rx2 wx2 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step c1: COMMIT;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+442368         
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+811008         
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx1 c1 rx2 wx2 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx1 rx2 c1 wx2 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx1 rx2 wx2 c1 rx3 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx1 rx2 wx2 rx3 c1 c2
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 at1 rx1 wx1 rx2 wx2 rx3 c2 c1
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: s1 rx1 at1 c1 wx1 rx2 wx2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+442368         
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+811008         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx1 c1 rx2 wx2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx1 rx2 c1 wx2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx1 rx2 wx2 c1 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx1 rx2 wx2 rx3 c1 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 at1 wx1 rx2 wx2 rx3 c2 c1
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: s1 rx1 wx1 at1 c1 rx2 wx2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 at1 rx2 c1 wx2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 at1 rx2 wx2 c1 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 at1 rx2 wx2 rx3 c1 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 at1 rx2 wx2 rx3 c2 c1
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 at1 c1 wx2 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 at1 wx2 c1 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 at1 wx2 rx3 c1 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 at1 wx2 rx3 c2 c1
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 wx2 at1 c1 rx3 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 wx2 at1 rx3 c1 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 wx2 at1 rx3 c2 c1
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 wx2 rx3 at1 c1 c2
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 wx2 rx3 at1 c2 c1
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: s1 rx1 wx1 rx2 wx2 rx3 c2 at1 c1
+step s1: BEGIN;
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+
+starting permutation: rx1 s1 at1 c1 wx1 rx2 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+442368         
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+811008         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx1 c1 rx2 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx1 rx2 c1 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx1 rx2 wx2 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx1 rx2 wx2 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 at1 wx1 rx2 wx2 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 s1 wx1 at1 c1 rx2 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 at1 rx2 c1 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 at1 rx2 wx2 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 at1 rx2 wx2 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 at1 rx2 wx2 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 at1 c1 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 at1 wx2 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 at1 wx2 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 at1 wx2 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 wx2 at1 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 wx2 at1 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 wx2 at1 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 wx2 rx3 at1 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 wx2 rx3 at1 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 s1 wx1 rx2 wx2 rx3 c2 at1 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step s1: BEGIN;
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 s1 at1 c1 rx2 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 at1 rx2 c1 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 at1 rx2 wx2 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 at1 rx2 wx2 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 at1 rx2 wx2 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 at1 c1 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 at1 wx2 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 at1 wx2 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 at1 wx2 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 wx2 at1 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 wx2 at1 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 wx2 at1 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 wx2 rx3 at1 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 wx2 rx3 at1 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 s1 rx2 wx2 rx3 c2 at1 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 at1 c1 wx2 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+475136         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 at1 wx2 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 at1 wx2 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 at1 wx2 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 wx2 at1 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 wx2 at1 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 wx2 at1 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 wx2 rx3 at1 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 wx2 rx3 at1 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 s1 wx2 rx3 c2 at1 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step s1: BEGIN;
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 s1 at1 c1 rx3 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 s1 at1 rx3 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 s1 at1 rx3 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 s1 rx3 at1 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 s1 rx3 at1 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 s1 rx3 c2 at1 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step s1: BEGIN;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 rx3 s1 at1 c1 c2
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+step c2: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 rx3 s1 at1 c2 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c2: COMMIT;
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 rx3 s1 c2 at1 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step s1: BEGIN;
+step c2: COMMIT;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
+
+starting permutation: rx1 wx1 rx2 wx2 rx3 c2 s1 at1 c1
+step rx1: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+65536          
+step wx1: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx2: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+98304          
+step wx2: INSERT INTO b SELECT generate_series(1,1000) % 4;
+step rx3: SELECT pg_total_relation_size('b');
+pg_total_relation_size
+
+139264         
+step c2: COMMIT;
+step s1: BEGIN;
+step at1: ALTER TABLE b SET (fillfactor = 10);
+step c1: COMMIT;
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 1e73b4a..6fd2fe1 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -23,4 +23,6 @@ test: multixact-no-deadlock
 test: multixact-no-forget
 test: propagate-lock-delete
 test: drop-index-concurrently-1
+test: alter-table-1
+test: alter-table-2
 test: timeouts
diff --git a/src/test/isolation/specs/alter-table-1.spec b/src/test/isolation/specs/alter-table-1.spec
new file mode 100644
index 0000000..f01084d
--- /dev/null
+++ b/src/test/isolation/specs/alter-table-1.spec
@@ -0,0 +1,34 @@
+# ALTER TABLE - Add and Validate constraint with concurrent writes
+#
+# ADD FOREIGN KEY allows a minimum of ShareRowExclusiveLock,
+# so we mix writes with it to see what works or waits.
+# VALIDATE allows a minimum of ShareUpdateExclusiveLock
+# so we mix reads with it to see what works or waits
+
+setup
+{
+ CREATE TABLE a (i int PRIMARY KEY);
+ CREATE TABLE b (a_id int);
+ INSERT INTO a VALUES (0), (1), (2), (3);
+ INSERT INTO b SELECT generate_series(1,1000) % 4;
+}
+
+teardown
+{
+ DROP TABLE a, b;
+}
+
+session "s1"
+step "s1"	{ BEGIN; }
+step "at1"	{ ALTER TABLE b ADD CONSTRAINT bfk FOREIGN KEY (a_id) REFERENCES a (i) NOT VALID; }
+step "sc1"	{ COMMIT; }
+step "s2"	{ BEGIN; }
+step "at2"	{ ALTER TABLE b VALIDATE CONSTRAINT bfk; }
+step "sc2"	{ COMMIT; }
+
+session "s2"
+setup		{ BEGIN; }
+step "rx1"	{ SELECT * FROM b WHERE a_id = 1 LIMIT 1; }
+step "wx"	{ INSERT INTO b VALUES (0); }
+step "rx1"	{ SELECT * FROM b WHERE a_id = 3 LIMIT 3; }
+step "c2"	{ COMMIT; }
diff --git a/src/test/isolation/specs/alter-table-2.spec b/src/test/isolation/specs/alter-table-2.spec
new file mode 100644
index 0000000..bd0f3ba
--- /dev/null
+++ b/src/test/isolation/specs/alter-table-2.spec
@@ -0,0 +1,29 @@
+# ALTER TABLE - Change fillfactor
+#
+# As the fillfactor takes effect the table grows much larger
+
+setup
+{
+ CREATE TABLE b (a_id int);
+ ALTER TABLE b SET (fillfactor = 100);
+ INSERT INTO b SELECT generate_series(1,1000) % 4;
+}
+
+teardown
+{
+ DROP TABLE b;
+}
+
+session "s1"
+step "s1"	{ BEGIN; }
+step "at1"	{ ALTER TABLE b SET (fillfactor = 10); }
+step "c1"	{ COMMIT; }
+
+session "s2"
+setup		{ BEGIN; }
+step "rx1"	{ SELECT pg_total_relation_size('b'); }
+step "wx1"	{ INSERT INTO b SELECT generate_series(1,1000) % 4; }
+step "rx2"	{ SELECT pg_total_relation_size('b'); }
+step "wx2"	{ INSERT INTO b SELECT generate_series(1,1000) % 4; }
+step "rx3"	{ SELECT pg_total_relation_size('b'); }
+step "c2"	{ COMMIT; }
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 0f0c638..e2307ff 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1840,76 +1840,87 @@ and relnamespace != (select oid from pg_namespace where nspname = 'pg_catalog')
 and c.relname != 'my_locks'
 group by c.relname;
 create table alterlock (f1 int primary key, f2 text);
+insert into alterlock values (1, 'foo');
+create table alterlock2 (f3 int primary key, f1 int);
+insert into alterlock2 values (1, 1);
 begin; alter table alterlock alter column f2 set statistics 150;
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 rollback;
 begin; alter table alterlock cluster on alterlock_pkey;
 select * from my_locks order by 1;
-    relname     |    max_lockmode     
-----------------+---------------------
- alterlock      | AccessExclusiveLock
- alterlock_pkey | AccessExclusiveLock
+    relname     |       max_lockmode       
+----------------+--------------------------
+ alterlock      | ShareUpdateExclusiveLock
+ alterlock_pkey | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set without cluster;
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
 (1 row)
 
 commit;
 begin; alter table alterlock set (fillfactor = 100);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock reset (fillfactor);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set (toast.autovacuum_enabled = off);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock set (autovacuum_enabled = off);
 select * from my_locks order by 1;
-  relname  |    max_lockmode     
------------+---------------------
- alterlock | AccessExclusiveLock
- pg_toast  | AccessExclusiveLock
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+ pg_toast  | ShareUpdateExclusiveLock
 (2 rows)
 
 commit;
 begin; alter table alterlock alter column f2 set (n_distinct = 1);
 select * from my_locks order by 1;
+  relname  |       max_lockmode       
+-----------+--------------------------
+ alterlock | ShareUpdateExclusiveLock
+(1 row)
+
+rollback;
+begin; alter table alterlock alter column f2 set storage extended;
+select * from my_locks order by 1;
   relname  |    max_lockmode     
 -----------+---------------------
  alterlock | AccessExclusiveLock
 (1 row)
 
 rollback;
-begin; alter table alterlock alter column f2 set storage extended;
+begin; alter table alterlock alter column f2 set default 'x';
 select * from my_locks order by 1;
   relname  |    max_lockmode     
 -----------+---------------------
@@ -1917,7 +1928,12 @@ select * from my_locks order by 1;
 (1 row)
 
 rollback;
-begin; alter table alterlock alter column f2 set default 'x';
+begin;
+create trigger ttdummy
+	before delete or update on alterlock
+	for each row
+	execute procedure
+	ttdummy (1, 1);
 select * from my_locks order by 1;
   relname  |    max_lockmode     
 -----------+---------------------
@@ -1925,7 +1941,48 @@ select * from my_locks order by 1;
 (1 row)
 
 rollback;
+begin;
+select * from my_locks order by 1;
+ relname | max_lockmode 
+---------+--------------
+(0 rows)
+
+alter table alterlock2 add foreign key (f1) references alterlock (f1);
+select * from my_locks order by 1;
+     relname     |    max_lockmode     
+-----------------+---------------------
+ alterlock       | AccessExclusiveLock
+ alterlock2      | AccessExclusiveLock
+ alterlock2_pkey | AccessShareLock
+ alterlock_pkey  | AccessShareLock
+(4 rows)
+
+rollback;
+begin;
+alter table alterlock2
+add constraint alterlock2nv foreign key (f1) references alterlock (f1) NOT VALID;
+select * from my_locks order by 1;
+  relname   |    max_lockmode     
+------------+---------------------
+ alterlock  | AccessExclusiveLock
+ alterlock2 | AccessExclusiveLock
+(2 rows)
+
+commit;
+begin;
+alter table alterlock2 validate constraint alterlock2nv;
+select * from my_locks order by 1;
+     relname     |       max_lockmode       
+-----------------+--------------------------
+ alterlock       | RowShareLock
+ alterlock2      | ShareUpdateExclusiveLock
+ alterlock2_pkey | AccessShareLock
+ alterlock_pkey  | AccessShareLock
+(4 rows)
+
+rollback;
 -- cleanup
+drop table alterlock2;
 drop table alterlock;
 drop view my_locks;
 drop type lockmodes;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 87973c1..a2ad863 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1283,6 +1283,9 @@ and c.relname != 'my_locks'
 group by c.relname;
 
 create table alterlock (f1 int primary key, f2 text);
+insert into alterlock values (1, 'foo');
+create table alterlock2 (f3 int primary key, f1 int);
+insert into alterlock2 values (1, 1);
 
 begin; alter table alterlock alter column f2 set statistics 150;
 select * from my_locks order by 1;
@@ -1324,7 +1327,33 @@ begin; alter table alterlock alter column f2 set default 'x';
 select * from my_locks order by 1;
 rollback;
 
+begin;
+create trigger ttdummy
+	before delete or update on alterlock
+	for each row
+	execute procedure
+	ttdummy (1, 1);
+select * from my_locks order by 1;
+rollback;
+
+begin;
+select * from my_locks order by 1;
+alter table alterlock2 add foreign key (f1) references alterlock (f1);
+select * from my_locks order by 1;
+rollback;
+
+begin;
+alter table alterlock2
+add constraint alterlock2nv foreign key (f1) references alterlock (f1) NOT VALID;
+select * from my_locks order by 1;
+commit;
+begin;
+alter table alterlock2 validate constraint alterlock2nv;
+select * from my_locks order by 1;
+rollback;
+
 -- cleanup
+drop table alterlock2;
 drop table alterlock;
 drop view my_locks;
 drop type lockmodes;
#100Vik Fearing
vik.fearing@dalibo.com
In reply to: Simon Riggs (#95)

On 03/06/2014 10:47 AM, Simon Riggs wrote:

On 5 March 2014 09:28, Simon Riggs <simon@2ndquadrant.com> wrote:

So that returns us to solving the catalog consistency problem in
pg_dump and similar applications.

No answer, guys, and time is ticking away here.

Sorry, I've accumulated several days of backlog (and it'll only get
worse in the coming week) so I haven't had time to look at all this in
the detail I wanted to.

I'd like to get a communal solution to this rather than just punting
the whole patch.

If we have to strip it down to the bar essentials, so be it. For me,
the biggest need here is to make VALIDATE CONSTRAINT take only a
ShareUpdateExclusiveLock while it runs. Almost everything else needs a
full AccessExclusiveLock anyway, or doesn't run for very long so isn't
a critical problem. (Perhaps we can then wrap ADD CONSTRAINT ... NOT
VALID and VALIDATE into a single command using the CONCURRENTLY
keyword so it runs two transactions to complete the task).

Validating FKs on big tables can take hours and it really isn't
acceptable for us to lock out access while we do that. FKs are
*supposed* to be a major reason people use RDBMS, so keeping them in a
state where they are effectively unusable is a major debilitating
point against adoption of PostgreSQL.

If there are issues with pg_dump we can just document them.

Guide me with your thoughts.

I think committing VALIDATE CONSTRAINT is essential for 9.4; the rest
can be delayed until 9.5. None of the discussion in this thread has
been about that subcommand, and I don't personally see a problem with it.

I don't care much about ADD CONSTRAINT CONCURRENTLY. If it's there,
fine. If it's not, that's fine, too.

My personal use case for this, and I even started writing a patch before
I realized I was re-writing this one, is adding a CHECK constraint NOT
VALID so that future commits respect it, then UPDATEing the existing
rows to "fix" them, and then VALIDATE CONSTRAINTing it. There is zero
need for an AccessExclusiveLock on that last step.

My original idea was to concurrently create a partial index on the "bad"
rows, and then validate the constraint using that index. The AEL would
only be held long enough to check if the index is empty or not.
Obviously, reducing the lock level is a cleaner solution, so I'd like to
see that happen.

--
Vik

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#101Simon Riggs
simon@2ndQuadrant.com
In reply to: Simon Riggs (#99)
Re: ALTER TABLE lock strength reduction patch is unsafe Reply-To:

On 8 March 2014 11:14, Simon Riggs <simon@2ndquadrant.com> wrote:

On 7 March 2014 09:04, Simon Riggs <simon@2ndquadrant.com> wrote:

The right thing to do here is to not push to the extremes. If we mess
too much with the ruleutil stuff it will just be buggy. A more
considered analysis in a later release is required for a full and
complete approach. As I indicated earlier, an 80/20 solution is better
for this release.

Slimming down the patch, I've removed changes to lock levels for
almost all variants. The only lock levels now reduced are those for
VALIDATE, plus setting of relation and attribute level options.

VALIDATE is implemented by calling pg_get_constraintdef_mvcc(), a
slightly modified variant of pg_get_constraintdef that uses the
transaction snapshot. I propose this rather than Noah's solution
solely because this will allow any user to request the MVCC data,
rather than implement a hack that only works for pg_dump. I will post
the patch later today.

Implemented in attached patch, v22

The following commands (only) are allowed with
ShareUpdateExclusiveLock, patch includes doc changes.

ALTER TABLE ... VALIDATE CONSTRAINT constraint_name
covered by isolation test, plus verified manually with pg_dump

ALTER TABLE ... ALTER COLUMN ... SET STATISTICS
ALTER TABLE ... ALTER COLUMN ... SET (...)
ALTER TABLE ... ALTER COLUMN ... RESET (...)

ALTER TABLE ... CLUSTER ON ...
ALTER TABLE ... SET WITHOUT CLUSTER
ALTER TABLE ... SET (...)
covered by isolation test

ALTER TABLE ... RESET (...)

ALTER INDEX ... SET (...)
ALTER INDEX ... RESET (...)

All other ALTER commands take AccessExclusiveLock

I commend this patch to you for final review; I would like to commit
this in a few days.

I'm planning to commit this today at 1500UTC barring objections or
negative reviews.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#102Noah Misch
noah@leadboat.com
In reply to: Simon Riggs (#101)
Re: ALTER TABLE lock strength reduction patch is unsafe Reply-To:

On Tue, Mar 18, 2014 at 10:39:03AM +0000, Simon Riggs wrote:

On 8 March 2014 11:14, Simon Riggs <simon@2ndquadrant.com> wrote:

Implemented in attached patch, v22

I commend this patch to you for final review; I would like to commit
this in a few days.

I'm planning to commit this today at 1500UTC barring objections or
negative reviews.

Not an objection, but FYI, I'm currently in the midst of reviewing this.

--
Noah Misch
EnterpriseDB http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#103Simon Riggs
simon@2ndQuadrant.com
In reply to: Noah Misch (#102)
Re: ALTER TABLE lock strength reduction patch is unsafe Reply-To:

On 18 March 2014 21:21, Noah Misch <noah@leadboat.com> wrote:

On Tue, Mar 18, 2014 at 10:39:03AM +0000, Simon Riggs wrote:

On 8 March 2014 11:14, Simon Riggs <simon@2ndquadrant.com> wrote:

Implemented in attached patch, v22

I commend this patch to you for final review; I would like to commit
this in a few days.

I'm planning to commit this today at 1500UTC barring objections or
negative reviews.

Not an objection, but FYI, I'm currently in the midst of reviewing this.

Thanks, I'll holdoff until I hear from you.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#104Vik Fearing
vik.fearing@dalibo.com
In reply to: Simon Riggs (#101)
1 attachment(s)
Re: ALTER TABLE lock strength reduction patch is unsafe Reply-To:

On 03/18/2014 11:39 AM, Simon Riggs wrote:

On 8 March 2014 11:14, Simon Riggs <simon@2ndquadrant.com> wrote:

On 7 March 2014 09:04, Simon Riggs <simon@2ndquadrant.com> wrote:

The right thing to do here is to not push to the extremes. If we mess
too much with the ruleutil stuff it will just be buggy. A more
considered analysis in a later release is required for a full and
complete approach. As I indicated earlier, an 80/20 solution is better
for this release.

Slimming down the patch, I've removed changes to lock levels for
almost all variants. The only lock levels now reduced are those for
VALIDATE, plus setting of relation and attribute level options.

VALIDATE is implemented by calling pg_get_constraintdef_mvcc(), a
slightly modified variant of pg_get_constraintdef that uses the
transaction snapshot. I propose this rather than Noah's solution
solely because this will allow any user to request the MVCC data,
rather than implement a hack that only works for pg_dump. I will post
the patch later today.

Implemented in attached patch, v22

The following commands (only) are allowed with
ShareUpdateExclusiveLock, patch includes doc changes.

ALTER TABLE ... VALIDATE CONSTRAINT constraint_name
covered by isolation test, plus verified manually with pg_dump

ALTER TABLE ... ALTER COLUMN ... SET STATISTICS
ALTER TABLE ... ALTER COLUMN ... SET (...)
ALTER TABLE ... ALTER COLUMN ... RESET (...)

ALTER TABLE ... CLUSTER ON ...
ALTER TABLE ... SET WITHOUT CLUSTER
ALTER TABLE ... SET (...)
covered by isolation test

ALTER TABLE ... RESET (...)

ALTER INDEX ... SET (...)
ALTER INDEX ... RESET (...)

All other ALTER commands take AccessExclusiveLock

I commend this patch to you for final review; I would like to commit
this in a few days.

I'm planning to commit this today at 1500UTC barring objections or
negative reviews.

At my current level of competence, this patch looks good to me. I'm
looking forward to reading Noah's review to see what I may have missed.

The attached patch fixes two typos in the code comments.

--
Vik

Attachments:

typos.patchtext/x-diff; name=typos.patchDownload
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 2939,2945 **** AlterTableGetLockLevel(List *cmds)
  
  				/*
  				 * These subcommands affect implicit row type conversion. They
! 				 * have affects similar to CREATE/DROP CAST on queries.
  				 * don't provide for invalidating parse trees as a result of
  				 * such changes, so we keep these at AccessExclusiveLock.
  				 */
--- 2939,2945 ----
  
  				/*
  				 * These subcommands affect implicit row type conversion. They
! 				 * have affects similar to CREATE/DROP CAST on queries.  We
  				 * don't provide for invalidating parse trees as a result of
  				 * such changes, so we keep these at AccessExclusiveLock.
  				 */
*** a/src/backend/utils/cache/relcache.c
--- b/src/backend/utils/cache/relcache.c
***************
*** 1889,1895 **** RelationDestroyRelation(Relation relation, bool remember_tupdesc)
  	if (--relation->rd_att->tdrefcount == 0)
  	{
  		/*
! 		 * If we Rebuilt a relcache entry during a transaction then its
  		 * possible we did that because the TupDesc changed as the result
  		 * of an ALTER TABLE that ran at less than AccessExclusiveLock.
  		 * It's possible someone copied that TupDesc, in which case the
--- 1889,1895 ----
  	if (--relation->rd_att->tdrefcount == 0)
  	{
  		/*
! 		 * If we Rebuilt a relcache entry during a transaction then it's
  		 * possible we did that because the TupDesc changed as the result
  		 * of an ALTER TABLE that ran at less than AccessExclusiveLock.
  		 * It's possible someone copied that TupDesc, in which case the
#105Noah Misch
noah@leadboat.com
In reply to: Simon Riggs (#99)
Re: ALTER TABLE lock strength reduction patch is unsafe Reply-To:

On Sat, Mar 08, 2014 at 11:14:30AM +0000, Simon Riggs wrote:

On 7 March 2014 09:04, Simon Riggs <simon@2ndquadrant.com> wrote:

The right thing to do here is to not push to the extremes. If we mess
too much with the ruleutil stuff it will just be buggy. A more
considered analysis in a later release is required for a full and
complete approach. As I indicated earlier, an 80/20 solution is better
for this release.

Slimming down the patch, I've removed changes to lock levels for
almost all variants. The only lock levels now reduced are those for
VALIDATE, plus setting of relation and attribute level options.

Good call.

The following commands (only) are allowed with
ShareUpdateExclusiveLock, patch includes doc changes.

ALTER TABLE ... VALIDATE CONSTRAINT constraint_name
covered by isolation test, plus verified manually with pg_dump

I found a pre-existing bug aggravated by this, which I will shortly report on
a new thread.

ALTER TABLE ... ALTER COLUMN ... SET STATISTICS
ALTER TABLE ... ALTER COLUMN ... SET (...)
ALTER TABLE ... ALTER COLUMN ... RESET (...)

ALTER TABLE ... CLUSTER ON ...
ALTER TABLE ... SET WITHOUT CLUSTER

A comment at check_index_is_clusterable() still mentions "exclusive lock".

ALTER TABLE ... SET (...)
covered by isolation test

ALTER TABLE ... RESET (...)

ALTER INDEX ... SET (...)
ALTER INDEX ... RESET (...)

See discussion below.

All other ALTER commands take AccessExclusiveLock

--- a/doc/src/sgml/mvcc.sgml
+++ b/doc/src/sgml/mvcc.sgml
@@ -865,7 +865,8 @@ ERROR:  could not serialize access due to read/write dependencies among transact
<para>
Acquired by <command>VACUUM</command> (without <option>FULL</option>),
<command>ANALYZE</>, <command>CREATE INDEX CONCURRENTLY</>, and
-         some forms of <command>ALTER TABLE</command>.
+         <command>ALTER TABLE VALIDATE</command> and other
+         <command>ALTER TABLE</command> variants that set options.

ALTER TABLE's documentation covers the details, so the old text sufficed for
here. I find "variants that set options" too vague, considering the nuances
of the actual list of affected forms.

</para>
</listitem>
</varlistentry>
@@ -951,7 +952,7 @@ ERROR: could not serialize access due to read/write dependencies among transact
</para>

<para>
-         Acquired by the <command>ALTER TABLE</>, <command>DROP TABLE</>,
+         Acquired by the <command>ALTER TABLE</> for rewriting, <command>DROP TABLE</>,
<command>TRUNCATE</command>, <command>REINDEX</command>,
<command>CLUSTER</command>, and <command>VACUUM FULL</command>
commands.

Forms that rewrite the table are just one class of examples. I would write
"Acquired by DROP TABLE, ..., VACUUM FULL, and some forms of ALTER TABLE."

--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -420,6 +439,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
index specification from the table.  This affects
future cluster operations that don't specify an index.
</para>
+     <para>
+      Changing cluster options uses a <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>
</listitem>
</varlistentry>
@@ -467,6 +489,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
FULL</>, <xref linkend="SQL-CLUSTER"> or one of the forms
of <command>ALTER TABLE</> that forces a table rewrite.
</para>
+     <para>
+      Changing storage parameters requires only a
+      <literal>SHARE UPDATE EXCLUSIVE</literal> lock.
+     </para>

Some places say "requires only", while others say "uses". Please adopt one of
those consistently. I somewhat prefer the latter.

@@ -1075,6 +1105,14 @@ ALTER TABLE distributors ADD CONSTRAINT distfk FOREIGN KEY (address) REFERENCES
</para>

<para>
+ To add a foreign key constraint to a table minimising impact on other work:

Our documentation has used the "minimize" spelling exclusively.

--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -52,7 +53,7 @@ static bool needs_toast_table(Relation rel);
* to end with CommandCounterIncrement if it makes any changes.
*/
void
-AlterTableCreateToastTable(Oid relOid, Datum reloptions)
+AlterTableCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode)
{
Relation	rel;

@@ -63,10 +64,10 @@ AlterTableCreateToastTable(Oid relOid, Datum reloptions)
* concurrent readers of the pg_class tuple won't have visibility issues,
* so let's be safe.
*/

The comment ending right here is falsified by the change.

-	rel = heap_open(relOid, AccessExclusiveLock);
+	rel = heap_open(relOid, lockmode);

We now request whatever lock our caller has already taken. If that is
AccessExclusiveLock, create_toast_table() could actually add a toast table.
Otherwise, it will either deduce that no change is required or raise an error.

@@ -161,6 +164,14 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, Datum reloptio
return false;

/*
+	 * We need to create a toast table. We shouldn't
+	 * have got this far if we're in ALTER TABLE unless
+	 * we are holding AccessExclusiveLock.
+	 */
+	if (lockmode < AccessExclusiveLock)
+		elog(ERROR, "AccessExclusiveLock required to add toast tables.");

Our model of lock levels has not regarded them as being ordered, so I
recommend "lockmode != AccessExclusiveLock". (I don't object to using
incidental order in AlterTableGetLockLevel(), where all the relevant levels
originate in the same function.)

--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2687,8 +2687,7 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
* The caller must lock the relation, with an appropriate lock level
* for the subcommands requested. Any subcommand that needs to rewrite
* tuples in the table forces the whole command to be executed with
- * AccessExclusiveLock (actually, that is currently required always, but
- * we hope to relax it at some point).	We pass the lock level down
+ * AccessExclusiveLock.  We pass the lock level down

The set of commands needing AccessExclusiveLock is much broader than those
rewriting the table. Consider replacing the quoted paragraph with something
like:

* The caller has locked the relation with "lockmode", which must be at least
* as strong as AlterTableGetLockLevel(stmt->cmds). We pass the lock level
* down so ...

@@ -2750,35 +2772,29 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse)
* ALTER TABLE RENAME which is treated as a different statement type T_RenameStmt
* and does not travel through this section of code and cannot be combined with
* any of the subcommands given here.
+ *
+ * Note that Hot Standby only knows about AccessExclusiveLocks on the master
+ * so any changes that might affect SELECTs running on standbys need to use
+ * AccessExclusiveLocks even if you think a lesser lock would do, unless you
+ * have a solution for that also.

Out of curiosity, do SELECTs on hot standbys impose known challenges in this
area not shared with local SELECTs?

+ *
+ * Also note that pg_dump uses only an AccessShareLock, meaning that anything
+ * that takes a lock less than AccessExclusiveLock can change object definitions
+ * while pg_dump is running. Be careful to check that the appropriate data is
+ * derived by pg_dump using an MVCC snapshot, rather than syscache lookups,
+ * otherwise we might end up with an inconsistent dump that can't restore.
+ *
+ * Be careful to ensure this function is called for Tables and Indexes only.
+ * It is not currently safe to be called for Views because security_barrier
+ * is listed as an option and so would be allowed to be set at a level lower
+ * than AccessExclusiveLock, which would not be correct.

This statement is accepted and takes only ShareUpdateExclusiveLock:

alter table information_schema.triggers set (security_barrier = true);

I suggest adding a LOCKMODE field to relopt_gen and adding a
reloptions_locklevel() function to determine the required level from an
options list. That puts control of the lock level near the code that
understands the implications for each option. You can then revert the
addition of AlterViewInternal() and some of the utility.c changes.

If I had to pick a reloption most likely to benefit from AccessExclusiveLock,
it would be user_catalog_table rather than security_barrier. I wonder if
output plugins would want a clear moment in the WAL stream -- the commit of
the transaction that sets the reloption -- after which they can assume that
all future records pertaining to the table in question have the needed bits.
If so, that implies AccessExclusiveLock for this reloption, because
heap_page_prune_opt() issues affected WAL records under AccessShareLock.

AT_ReplaceRelOptions could require the highest lock level required by any
option. It usage seems to be limited to CREATE OR REPLACE VIEW, so I would
give it AccessExclusiveLock and be done.

- * 2. Relcache needs to be internally consistent, so unless we lock the
- * definition during reads we have no way to guarantee that.

I looked for hazards like this, but I found none in the ALTER forms covered by
this patch. None of them modify multiple catalog rows affecting the same
relcache entry. However, thinking about that did lead me to ponder another
class of hazards. When backends can use one or more relations concurrently
with a DDL operation affecting those relations, those backends can find
themselves running with a subset of the catalog changes made within a
particular DDL operation. Consider VALIDATE CONSTRAINT against an inherited
constraint of an inheritance parent. It validates child table constraints,
modifying one catalog row per table. At COMMIT time, we queue sinval messages
for all affected tables. We add to the queue in atomic groups of
WRITE_QUANTUM (64) messages. Between two such groups joining the queue,
another backend may process the first group of messages. If the original DDL
used AccessExclusiveLock, this is always harmless. The DDL-issuing backend
still holds its lock, which means the inval-accepting backend must not have
the relation open. If the inval-accepting backend later opens the affected
relation, it will first acquire some lock and process the rest of the
invalidations from the DDL operation. When doing DDL under a weaker lock, the
inval-accepting backend might apply half the invalidations and immediately use
them in the context of an open relation. For VALIDATE CONSTRAINT, this means
a backend might briefly recognize only a subset of the inheritance tree
becoming valid. (I did not actually build a test case to confirm this.)

Considering that constraint exclusion is the sole consumer of
convalidated/ccvalid that can run in parallel with VALIDATE CONSTRAINT, I
think this is harmless. I did not find problems of this nature in any ALTER
TABLE forms affected by the patch. Let's just keep it in mind during future
lock level changes.

--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
static char *
pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
-							int prettyFlags)
+							int prettyFlags, bool useSyscache)
{
HeapTuple	tup;
Form_pg_constraint conForm;
StringInfoData buf;
+	SysScanDesc	scandesc;
+	ScanKeyData scankey[1];
+	Relation	relation;

gcc 4.2.4 gives me these warnings:

ruleutils.c: In function `pg_get_constraintdef_worker':
ruleutils.c:1318: warning: `relation' may be used uninitialized in this function
ruleutils.c:1316: warning: `scandesc' may be used uninitialized in this function

+
+	if (useSyscache)
+		tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constraintId));
+	else
+	{
+		Snapshot    snapshot = RegisterSnapshot(GetTransactionSnapshot());
+		PushActiveSnapshot(snapshot);

There's no need to push the snapshot; registration is enough.

+
+		relation = heap_open(ConstraintRelationId, AccessShareLock);
+
+		ScanKeyInit(&scankey[0],
+					ObjectIdAttributeNumber,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(constraintId));
+
+		scandesc = systable_beginscan(relation,
+										ConstraintOidIndexId,
+										true,
+										snapshot,
+										1,
+										scankey);
+		tup = systable_getnext(scandesc);
+
+		PopActiveSnapshot();
+		UnregisterSnapshot(snapshot);
+	}

Later code uses SysCacheGetAttr() regardless of how we acquired the tuple.
That works, but it deserves a brief comment mention.

pg_get_constraintdef_mvcc() still does syscache lookups by way of
decompile_column_index_array(), get_constraint_index(), and
deparse_expression_pretty(). It uses MVCC for things that matter for pg_dump
vs. reduced lock levels, but not comprehensively. I recommend not adding a
new function and instead changing pg_get_constraintdef() to use the
transaction snapshot unconditionally. It's hard to imagine an application
author making a reasoned choice between the two. Our in-tree callers, psql
and pg_dump, have no cause to prefer the less-MVCC behavior at any time.

--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -161,6 +161,14 @@ static bool eoxact_list_overflowed = false;
eoxact_list_overflowed = true; \
} while (0)
+/*
+ * EOXactTupleDescArray stores *TupleDescs that (might) need AtEOXact

Stray asterisk.

@@ -2312,6 +2334,37 @@ RelationCloseSmgrByOid(Oid relationId)
RelationCloseSmgr(relation);
}

+void
+RememberToFreeTupleDescAtEOX(TupleDesc td)
+{
+	if (EOXactTupleDescArray == NULL)
+	{
+		MemoryContext	oldcxt;
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+		EOXactTupleDescArray = (TupleDesc *) palloc(16 * sizeof(TupleDesc));
+		EOXactTupleDescArrayLen = 16;
+		NextEOXactTupleDescNum = 0;
+		MemoryContextSwitchTo(oldcxt);
+	}
+	else if (NextEOXactTupleDescNum >= EOXactTupleDescArrayLen)
+	{
+		MemoryContext	oldcxt;
+		int32       	newlen = EOXactTupleDescArrayLen * 2;
+
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+		Assert(EOXactTupleDescArrayLen > 0);
+
+		EOXactTupleDescArray = (TupleDesc *) repalloc(EOXactTupleDescArray,
+														newlen * sizeof(TupleDesc));
+		EOXactTupleDescArrayLen = newlen;
+		MemoryContextSwitchTo(oldcxt);

No need to switch contexts for repalloc().

--- /dev/null
+++ b/src/test/isolation/specs/alter-table-1.spec
@@ -0,0 +1,34 @@
+# ALTER TABLE - Add and Validate constraint with concurrent writes
+#
+# ADD FOREIGN KEY allows a minimum of ShareRowExclusiveLock,
+# so we mix writes with it to see what works or waits.
+# VALIDATE allows a minimum of ShareUpdateExclusiveLock
+# so we mix reads with it to see what works or waits

This comment is obsolete regarding the ADD FOREIGN KEY lock type.

Thanks,
nm

--
Noah Misch
EnterpriseDB http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#106Simon Riggs
simon@2ndQuadrant.com
In reply to: Noah Misch (#105)
Re: ALTER TABLE lock strength reduction patch is unsafe Reply-To:

On 21 March 2014 03:45, Noah Misch <noah@leadboat.com> wrote:

On Sat, Mar 08, 2014 at 11:14:30AM +0000, Simon Riggs wrote:

On 7 March 2014 09:04, Simon Riggs <simon@2ndquadrant.com> wrote:

The right thing to do here is to not push to the extremes. If we mess
too much with the ruleutil stuff it will just be buggy. A more
considered analysis in a later release is required for a full and
complete approach. As I indicated earlier, an 80/20 solution is better
for this release.

Slimming down the patch, I've removed changes to lock levels for
almost all variants. The only lock levels now reduced are those for
VALIDATE, plus setting of relation and attribute level options.

Good call.

Thanks for the review. I'll respond to each point on a later email but
looks nothing much major, apart from the point raised on separate
thread.

+ * Be careful to ensure this function is called for Tables and Indexes only.
+ * It is not currently safe to be called for Views because security_barrier
+ * is listed as an option and so would be allowed to be set at a level lower
+ * than AccessExclusiveLock, which would not be correct.

This statement is accepted and takes only ShareUpdateExclusiveLock:

alter table information_schema.triggers set (security_barrier = true);

I find it hard to justify why we accept such a statement. Surely its a
bug when the named table turns out to be a view? Presumably ALTER
SEQUENCE and ALTER <other stuff> has checks for the correct object
type? OMG.

I suggest adding a LOCKMODE field to relopt_gen and adding a
reloptions_locklevel() function to determine the required level from an
options list. That puts control of the lock level near the code that
understands the implications for each option. You can then revert the
addition of AlterViewInternal() and some of the utility.c changes.

Sure, that's how we code it, but I'm not sure we should introduce that
feature. The above weirdness is not itself justification.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#107Noah Misch
noah@leadboat.com
In reply to: Simon Riggs (#106)
Re: ALTER TABLE lock strength reduction patch is unsafe Reply-To:

On Fri, Mar 21, 2014 at 04:11:12PM +0000, Simon Riggs wrote:

On 21 March 2014 03:45, Noah Misch <noah@leadboat.com> wrote:

On Sat, Mar 08, 2014 at 11:14:30AM +0000, Simon Riggs wrote:

Thanks for the review. I'll respond to each point on a later email but
looks nothing much major, apart from the point raised on separate
thread.

Yep.

+ * Be careful to ensure this function is called for Tables and Indexes only.
+ * It is not currently safe to be called for Views because security_barrier
+ * is listed as an option and so would be allowed to be set at a level lower
+ * than AccessExclusiveLock, which would not be correct.

This statement is accepted and takes only ShareUpdateExclusiveLock:

alter table information_schema.triggers set (security_barrier = true);

I find it hard to justify why we accept such a statement. Surely its a
bug when the named table turns out to be a view? Presumably ALTER
SEQUENCE and ALTER <other stuff> has checks for the correct object
type? OMG.

We've framed ALTER TABLE's relkind leniency as a historic artifact. As a move
toward stricter checks, ALTER TABLE refused to operate on foreign tables in
9.1 and 9.2. 9.3 reversed that course, though. For better or worse, ALTER
TABLE is nearly a union of the relation ALTER possibilities. That choice is
well-entrenched.

--
Noah Misch
EnterpriseDB http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#108Simon Riggs
simon@2ndQuadrant.com
In reply to: Noah Misch (#107)
Re: ALTER TABLE lock strength reduction patch is unsafe Reply-To:

On 21 March 2014 17:49, Noah Misch <noah@leadboat.com> wrote:

+ * Be careful to ensure this function is called for Tables and Indexes only.
+ * It is not currently safe to be called for Views because security_barrier
+ * is listed as an option and so would be allowed to be set at a level lower
+ * than AccessExclusiveLock, which would not be correct.

This statement is accepted and takes only ShareUpdateExclusiveLock:

alter table information_schema.triggers set (security_barrier = true);

I find it hard to justify why we accept such a statement. Surely its a
bug when the named table turns out to be a view? Presumably ALTER
SEQUENCE and ALTER <other stuff> has checks for the correct object
type? OMG.

We've framed ALTER TABLE's relkind leniency as a historic artifact. As a move
toward stricter checks, ALTER TABLE refused to operate on foreign tables in
9.1 and 9.2. 9.3 reversed that course, though. For better or worse, ALTER
TABLE is nearly a union of the relation ALTER possibilities. That choice is
well-entrenched.

By "well entrenched", I think you mean undocumented, untested, unintentional?

Do we think anyone *relies* on being able to say the word TABLE when
in fact they mean VIEW or SEQUENCE?

How is that artefact anything but a bug? i.e. is anyone going to stop
me fixing it?

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#109Simon Riggs
simon@2ndQuadrant.com
In reply to: Noah Misch (#105)
Re: ALTER TABLE lock strength reduction patch is unsafe Reply-To:

On 21 March 2014 03:45, Noah Misch <noah@leadboat.com> wrote:

+ * Note that Hot Standby only knows about AccessExclusiveLocks on the master
+ * so any changes that might affect SELECTs running on standbys need to use
+ * AccessExclusiveLocks even if you think a lesser lock would do, unless you
+ * have a solution for that also.

Out of curiosity, do SELECTs on hot standbys impose known challenges in this
area not shared with local SELECTs?

No, but locks less than AccessExclusiveLock won't happen at all, so
its a difference that if improperly handled could cause a bug.

Plus I wanted to indicate I'd thought about it.

- * 2. Relcache needs to be internally consistent, so unless we lock the
- * definition during reads we have no way to guarantee that.

I looked for hazards like this, but I found none in the ALTER forms covered by
this patch. None of them modify multiple catalog rows affecting the same
relcache entry. However, thinking about that did lead me to ponder another
class of hazards. When backends can use one or more relations concurrently
with a DDL operation affecting those relations, those backends can find
themselves running with a subset of the catalog changes made within a
particular DDL operation. Consider VALIDATE CONSTRAINT against an inherited
constraint of an inheritance parent. It validates child table constraints,
modifying one catalog row per table. At COMMIT time, we queue sinval messages
for all affected tables. We add to the queue in atomic groups of
WRITE_QUANTUM (64) messages. Between two such groups joining the queue,
another backend may process the first group of messages. If the original DDL
used AccessExclusiveLock, this is always harmless. The DDL-issuing backend
still holds its lock, which means the inval-accepting backend must not have
the relation open. If the inval-accepting backend later opens the affected
relation, it will first acquire some lock and process the rest of the
invalidations from the DDL operation. When doing DDL under a weaker lock, the
inval-accepting backend might apply half the invalidations and immediately use
them in the context of an open relation. For VALIDATE CONSTRAINT, this means
a backend might briefly recognize only a subset of the inheritance tree
becoming valid. (I did not actually build a test case to confirm this.)

Considering that constraint exclusion is the sole consumer of
convalidated/ccvalid that can run in parallel with VALIDATE CONSTRAINT, I
think this is harmless. I did not find problems of this nature in any ALTER
TABLE forms affected by the patch. Let's just keep it in mind during future
lock level changes.

I'll document

pg_get_constraintdef_mvcc() still does syscache lookups by way of
decompile_column_index_array(), get_constraint_index(), and
deparse_expression_pretty(). It uses MVCC for things that matter for pg_dump
vs. reduced lock levels, but not comprehensively. I recommend not adding a
new function and instead changing pg_get_constraintdef() to use the
transaction snapshot unconditionally.

OK

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#110Noah Misch
noah@leadboat.com
In reply to: Simon Riggs (#108)
Re: ALTER TABLE lock strength reduction patch is unsafe Reply-To:

On Fri, Mar 21, 2014 at 06:53:27PM +0000, Simon Riggs wrote:

On 21 March 2014 17:49, Noah Misch <noah@leadboat.com> wrote:

alter table information_schema.triggers set (security_barrier = true);

I find it hard to justify why we accept such a statement. Surely its a
bug when the named table turns out to be a view? Presumably ALTER
SEQUENCE and ALTER <other stuff> has checks for the correct object
type? OMG.

We've framed ALTER TABLE's relkind leniency as a historic artifact. As a move
toward stricter checks, ALTER TABLE refused to operate on foreign tables in
9.1 and 9.2. 9.3 reversed that course, though. For better or worse, ALTER
TABLE is nearly a union of the relation ALTER possibilities. That choice is
well-entrenched.

By "well entrenched", I think you mean undocumented, untested, unintentional?

It's deliberate; a -hackers discussion revisits it perhaps once a year. The
ALTER VIEW documentation says:

For historical reasons, ALTER TABLE can be used with views too; but the only
variants of ALTER TABLE that are allowed with views are equivalent to the
ones shown above.

ALTER INDEX and ALTER SEQUENCE say something similar.

Do we think anyone *relies* on being able to say the word TABLE when
in fact they mean VIEW or SEQUENCE?

pg_dump emits statements that exercise it:

psql -c 'create view v as select 1 as c; alter view v alter c set default 0;'
pg_dump --table v | grep ALTER

How is that artefact anything but a bug? i.e. is anyone going to stop
me fixing it?

It's not the behavior I would choose for a new product, but I can't see
benefits sufficient to overturn previous decisions to keep it.

--
Noah Misch
EnterpriseDB http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#111Simon Riggs
simon@2ndQuadrant.com
In reply to: Noah Misch (#110)
Re: ALTER TABLE lock strength reduction patch is unsafe Reply-To:

On 21 March 2014 20:58, Noah Misch <noah@leadboat.com> wrote:

On Fri, Mar 21, 2014 at 06:53:27PM +0000, Simon Riggs wrote:

On 21 March 2014 17:49, Noah Misch <noah@leadboat.com> wrote:

alter table information_schema.triggers set (security_barrier = true);

I find it hard to justify why we accept such a statement. Surely its a
bug when the named table turns out to be a view? Presumably ALTER
SEQUENCE and ALTER <other stuff> has checks for the correct object
type? OMG.

We've framed ALTER TABLE's relkind leniency as a historic artifact. As a move
toward stricter checks, ALTER TABLE refused to operate on foreign tables in
9.1 and 9.2. 9.3 reversed that course, though. For better or worse, ALTER
TABLE is nearly a union of the relation ALTER possibilities. That choice is
well-entrenched.

By "well entrenched", I think you mean undocumented, untested, unintentional?

It's deliberate; a -hackers discussion revisits it perhaps once a year. The
ALTER VIEW documentation says:

For historical reasons, ALTER TABLE can be used with views too; but the only
variants of ALTER TABLE that are allowed with views are equivalent to the
ones shown above.

ALTER INDEX and ALTER SEQUENCE say something similar.

Do we think anyone *relies* on being able to say the word TABLE when
in fact they mean VIEW or SEQUENCE?

pg_dump emits statements that exercise it:

psql -c 'create view v as select 1 as c; alter view v alter c set default 0;'
pg_dump --table v | grep ALTER

How is that artefact anything but a bug? i.e. is anyone going to stop
me fixing it?

It's not the behavior I would choose for a new product, but I can't see
benefits sufficient to overturn previous decisions to keep it.

Speechless

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#112Tom Lane
tgl@sss.pgh.pa.us
In reply to: Simon Riggs (#111)
Re: ALTER TABLE lock strength reduction patch is unsafe Reply-To:

Simon Riggs <simon@2ndQuadrant.com> writes:

On 21 March 2014 20:58, Noah Misch <noah@leadboat.com> wrote:

It's not the behavior I would choose for a new product, but I can't see
benefits sufficient to overturn previous decisions to keep it.

Speechless

The key argument for not "fixing" this is that it would break existing
pg_dump files. That's a pretty hard argument to overcome, unfortunately,
even if you're willing to blow off the possibility that client
applications might contain similar shortcuts. We still do our best to
read dump files from the 7.0 era (see ConvertTriggerToFK() for one example
of going above and beyond for that); and every so often we do hear of
people trying to get data out of such ancient servers. So even if you
went and fixed pg_dump tomorrow, it'd probably be ten or fifteen years
before people would let you stop reading dumps from existing versions.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#113Simon Riggs
simon@2ndQuadrant.com
In reply to: Tom Lane (#112)
Re: ALTER TABLE lock strength reduction patch is unsafe Reply-To:

On 21 March 2014 23:36, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Simon Riggs <simon@2ndQuadrant.com> writes:

On 21 March 2014 20:58, Noah Misch <noah@leadboat.com> wrote:

It's not the behavior I would choose for a new product, but I can't see
benefits sufficient to overturn previous decisions to keep it.

Speechless

The key argument for not "fixing" this is that it would break existing
pg_dump files. That's a pretty hard argument to overcome, unfortunately,
even if you're willing to blow off the possibility that client
applications might contain similar shortcuts. We still do our best to
read dump files from the 7.0 era (see ConvertTriggerToFK() for one example
of going above and beyond for that); and every so often we do hear of
people trying to get data out of such ancient servers. So even if you
went and fixed pg_dump tomorrow, it'd probably be ten or fifteen years
before people would let you stop reading dumps from existing versions.

Noah had already convinced me, but thank you for explaining the issue
behind that.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#114Simon Riggs
simon@2ndQuadrant.com
In reply to: Simon Riggs (#106)
Re: ALTER TABLE lock strength reduction patch is unsafe Reply-To:

On 21 March 2014 16:11, Simon Riggs <simon@2ndquadrant.com> wrote:

+ * Be careful to ensure this function is called for Tables and Indexes only.
+ * It is not currently safe to be called for Views because security_barrier
+ * is listed as an option and so would be allowed to be set at a level lower
+ * than AccessExclusiveLock, which would not be correct.

This statement is accepted and takes only ShareUpdateExclusiveLock:

alter table information_schema.triggers set (security_barrier = true);

I suggest adding a LOCKMODE field to relopt_gen and adding a
reloptions_locklevel() function to determine the required level from an
options list. That puts control of the lock level near the code that
understands the implications for each option. You can then revert the
addition of AlterViewInternal() and some of the utility.c changes.

Sure, that's how we code it, but I'm not sure we should introduce that
feature. The above weirdness is not itself justification.

OK, will follow this path. It's a good idea since it encourages
authors of new "options" to consider properly the lock level that will
be required.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#115Jim Nasby
jim@nasby.net
In reply to: Simon Riggs (#38)

On 2/26/14, 9:15 AM, Simon Riggs wrote:

On 26 February 2014 13:38, Andres Freund<andres@2ndquadrant.com> wrote:

Hi,

On 2014-02-26 07:32:45 +0000, Simon Riggs wrote:

* This definitely should include isolationtester tests actually
performing concurrent ALTER TABLEs. All that's currently there is
tests that the locklevel isn't too high, but not that it actually works.

There is no concurrent behaviour here, hence no code that would be
exercised by concurrent tests.

Huh? There's most definitely new concurrent behaviour. Previously no
other backends could have a relation open (and locked) while it got
altered (which then sends out relcache invalidations). That's something
that should be tested.

It has been. High volume concurrent testing has been performed, per
Tom's original discussion upthread, but that's not part of the test
suite.
For other tests I have no guide as to how to write a set of automated
regression tests. Anything could cause a failure, so I'd need to write
an infinite set of tests to prove there is no bug*somewhere*. How
many tests are required? 0, 1, 3, 30?

ISTM that we don't want hand-written tests here, but rather generated tests that actually hit all potential cases. Obviously we'd never run that as part of normal reqression, but farm animals certainly could.
--
Jim C. Nasby, Data Architect jim@nasby.net
512.569.9461 (cell) http://jim.nasby.net

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers