at1.3-tablecmds-enum.patch

text/plain

Filename: at1.3-tablecmds-enum.patch
Type: text/plain
Part: 2
Message: Re: ALTER TYPE 2: skip already-provable no-work rewrites

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: unified
File+
src/backend/commands/tablecmds.c 0 0
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 78262d8..63e2538 100644
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 129,134 **** static List *on_commits = NIL;
--- 129,137 ----
  #define AT_PASS_MISC			8		/* other stuff */
  #define AT_NUM_PASSES			9
  
+ /* Level of effort required in Phase 3 (ATRewriteTables). */
+ typedef enum { WORK_NONE = 0, WORK_SCAN = 1, WORK_REWRITE = 2 } WorkLevel;
+ 
  typedef struct AlteredTableInfo
  {
  	/* Information saved before any work commences: */
***************
*** 141,147 **** typedef struct AlteredTableInfo
  	List	   *constraints;	/* List of NewConstraint */
  	List	   *newvals;		/* List of NewColumnValue */
  	bool		new_notnull;	/* T if we added new NOT NULL constraints */
! 	bool		new_changeoids; /* T if we added/dropped the OID column */
  	Oid			newTableSpace;	/* new tablespace; 0 means no change */
  	/* Objects to rebuild after completing ALTER TYPE operations */
  	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
--- 144,150 ----
  	List	   *constraints;	/* List of NewConstraint */
  	List	   *newvals;		/* List of NewColumnValue */
  	bool		new_notnull;	/* T if we added new NOT NULL constraints */
! 	WorkLevel	worklevel;		/* How much work shall we do on the heap? */
  	Oid			newTableSpace;	/* new tablespace; 0 means no change */
  	/* Objects to rebuild after completing ALTER TYPE operations */
  	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
***************
*** 3180,3265 **** ATRewriteTables(List **wqueue, LOCKMODE lockmode)
  		if (tab->relkind == RELKIND_FOREIGN_TABLE)
  			continue;
  
! 		/*
! 		 * We only need to rewrite the table if at least one column needs to
! 		 * be recomputed, or we are adding/removing the OID column.
! 		 */
! 		if (tab->newvals != NIL || tab->new_changeoids)
  		{
! 			/* Build a temporary relation and copy data */
! 			Relation	OldHeap;
! 			Oid			OIDNewHeap;
! 			Oid			NewTableSpace;
  
! 			OldHeap = heap_open(tab->relid, NoLock);
  
! 			/*
! 			 * We don't support rewriting of system catalogs; there are too
! 			 * many corner cases and too little benefit.  In particular this
! 			 * is certainly not going to work for mapped catalogs.
! 			 */
! 			if (IsSystemRelation(OldHeap))
! 				ereport(ERROR,
! 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 						 errmsg("cannot rewrite system relation \"%s\"",
! 								RelationGetRelationName(OldHeap))));
  
! 			/*
! 			 * Don't allow rewrite on temp tables of other backends ... their
! 			 * local buffer manager is not going to cope.
! 			 */
! 			if (RELATION_IS_OTHER_TEMP(OldHeap))
! 				ereport(ERROR,
! 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 				errmsg("cannot rewrite temporary tables of other sessions")));
  
! 			/*
! 			 * Select destination tablespace (same as original unless user
! 			 * requested a change)
! 			 */
! 			if (tab->newTableSpace)
! 				NewTableSpace = tab->newTableSpace;
! 			else
! 				NewTableSpace = OldHeap->rd_rel->reltablespace;
  
! 			heap_close(OldHeap, NoLock);
  
! 			/* Create transient table that will receive the modified data */
! 			OIDNewHeap = make_new_heap(tab->relid, NewTableSpace);
  
! 			/*
! 			 * Copy the heap data into the new table with the desired
! 			 * modifications, and test the current data within the table
! 			 * against new constraints generated by ALTER TABLE commands.
! 			 */
! 			ATRewriteTable(tab, OIDNewHeap, lockmode);
  
! 			/*
! 			 * Swap the physical files of the old and new heaps, then rebuild
! 			 * indexes and discard the old heap.  We can use RecentXmin for
! 			 * the table's new relfrozenxid because we rewrote all the tuples
! 			 * in ATRewriteTable, so no older Xid remains in the table.  Also,
! 			 * we never try to swap toast tables by content, since we have no
! 			 * interest in letting this code work on system catalogs.
! 			 */
! 			finish_heap_swap(tab->relid, OIDNewHeap,
! 							 false, false, true, RecentXmin);
! 		}
! 		else
! 		{
! 			/*
! 			 * Test the current data within the table against new constraints
! 			 * generated by ALTER TABLE commands, but don't rebuild data.
! 			 */
! 			if (tab->constraints != NIL || tab->new_notnull)
  				ATRewriteTable(tab, InvalidOid, lockmode);
  
! 			/*
! 			 * If we had SET TABLESPACE but no reason to reconstruct tuples,
! 			 * just do a block-by-block copy.
! 			 */
! 			if (tab->newTableSpace)
! 				ATExecSetTableSpace(tab->relid, tab->newTableSpace, lockmode);
  		}
  	}
  
--- 3183,3271 ----
  		if (tab->relkind == RELKIND_FOREIGN_TABLE)
  			continue;
  
! 		/* New NOT NULL constraints always require a scan. */
! 		if (tab->new_notnull)
! 			tab->worklevel = Max(tab->worklevel, WORK_SCAN);
! 
! 		switch (tab->worklevel)
  		{
! 			case WORK_REWRITE:
! 				{
! 					/* Build a temporary relation and copy data */
! 					Relation	OldHeap;
! 					Oid			OIDNewHeap;
! 					Oid			NewTableSpace;
  
! 					OldHeap = heap_open(tab->relid, NoLock);
  
! 					/*
! 					 * We don't support rewriting of system catalogs; there are
! 					 * too many corner cases and too little benefit.  In
! 					 * particular, it would fail for mapped catalogs.
! 					 */
! 					if (IsSystemRelation(OldHeap))
! 						ereport(ERROR,
! 								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 								 errmsg("cannot rewrite system relation \"%s\"",
! 										RelationGetRelationName(OldHeap))));
  
! 					/*
! 					 * Don't allow rewrite on temp tables of other backends
! 					 * ... their local buffer manager is not going to cope.
! 					 */
! 					if (RELATION_IS_OTHER_TEMP(OldHeap))
! 						ereport(ERROR,
! 								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 								 errmsg("cannot rewrite temporary tables of other sessions")));
  
! 					/*
! 					 * Select destination tablespace (same as original unless
! 					 * user requested a change)
! 					 */
! 					if (tab->newTableSpace)
! 						NewTableSpace = tab->newTableSpace;
! 					else
! 						NewTableSpace = OldHeap->rd_rel->reltablespace;
  
! 					heap_close(OldHeap, NoLock);
  
! 					/* Create transient table for the modified data */
! 					OIDNewHeap = make_new_heap(tab->relid, NewTableSpace);
  
! 					/*
! 					 * Copy the heap data into the new table with the desired
! 					 * modifications, and test the current data within the table
! 					 * against new constraints.
! 					 */
! 					ATRewriteTable(tab, OIDNewHeap, lockmode);
  
! 					/*
! 					 * Swap the physical files of the old and new heaps, then
! 					 * rebuild indexes and discard the old heap.  We can use
! 					 * RecentXmin for the table's new relfrozenxid because we
! 					 * rewrote all the tuples in ATRewriteTable, so no older Xid
! 					 * remains in the table.  Also, we never try to swap toast
! 					 * tables by content, since we have no interest in letting
! 					 * this code work on system catalogs.
! 					 */
! 					finish_heap_swap(tab->relid, OIDNewHeap,
! 									 false, false, true, RecentXmin);
! 				}
! 				break;
! 
! 			case WORK_SCAN:
! 				/* Test the current table data. */
  				ATRewriteTable(tab, InvalidOid, lockmode);
+ 				break;
  
! 			case WORK_NONE:
! 				/*
! 				 * If we had SET TABLESPACE but no reason to reconstruct tuples,
! 				 * just do a block-by-block copy.
! 				 */
! 				if (tab->newTableSpace)
! 					ATExecSetTableSpace(tab->relid, tab->newTableSpace, lockmode);
! 				break;
  		}
  	}
  
***************
*** 3320,3326 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
  	Relation	newrel;
  	TupleDesc	oldTupDesc;
  	TupleDesc	newTupDesc;
- 	bool		needscan = false;
  	List	   *notnull_attrs;
  	int			i;
  	ListCell   *l;
--- 3326,3331 ----
***************
*** 3328,3333 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
--- 3333,3348 ----
  	CommandId	mycid;
  	BulkInsertState bistate;
  	int			hi_options;
+ 	ExprContext *econtext;
+ 	Datum	   *values;
+ 	bool	   *isnull;
+ 	TupleTableSlot *oldslot;
+ 	TupleTableSlot *newslot;
+ 	HeapScanDesc scan;
+ 	HeapTuple	tuple;
+ 	MemoryContext oldCxt;
+ 	List	   *dropped_attrs = NIL;
+ 	ListCell   *lc;
  
  	/*
  	 * Open the relation(s).  We have surely already locked the existing
***************
*** 3379,3385 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
  		switch (con->contype)
  		{
  			case CONSTR_CHECK:
- 				needscan = true;
  				con->qualstate = (List *)
  					ExecPrepareExpr((Expr *) con->qual, estate);
  				break;
--- 3394,3399 ----
***************
*** 3414,3580 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
  				!newTupDesc->attrs[i]->attisdropped)
  				notnull_attrs = lappend_int(notnull_attrs, i);
  		}
- 		if (notnull_attrs)
- 			needscan = true;
  	}
  
! 	if (newrel || needscan)
! 	{
! 		ExprContext *econtext;
! 		Datum	   *values;
! 		bool	   *isnull;
! 		TupleTableSlot *oldslot;
! 		TupleTableSlot *newslot;
! 		HeapScanDesc scan;
! 		HeapTuple	tuple;
! 		MemoryContext oldCxt;
! 		List	   *dropped_attrs = NIL;
! 		ListCell   *lc;
! 
! 		if (newrel)
! 			ereport(DEBUG1,
! 					(errmsg("Rewriting table \"%s\"",
! 							RelationGetRelationName(oldrel))));
! 		else
! 			ereport(DEBUG1,
! 					(errmsg("Verifying table \"%s\"",
! 							RelationGetRelationName(oldrel))));
  
! 		econtext = GetPerTupleExprContext(estate);
  
! 		/*
! 		 * Make tuple slots for old and new tuples.  Note that even when the
! 		 * tuples are the same, the tupDescs might not be (consider ADD COLUMN
! 		 * without a default).
! 		 */
! 		oldslot = MakeSingleTupleTableSlot(oldTupDesc);
! 		newslot = MakeSingleTupleTableSlot(newTupDesc);
  
! 		/* Preallocate values/isnull arrays */
! 		i = Max(newTupDesc->natts, oldTupDesc->natts);
! 		values = (Datum *) palloc(i * sizeof(Datum));
! 		isnull = (bool *) palloc(i * sizeof(bool));
! 		memset(values, 0, i * sizeof(Datum));
! 		memset(isnull, true, i * sizeof(bool));
  
! 		/*
! 		 * Any attributes that are dropped according to the new tuple
! 		 * descriptor can be set to NULL. We precompute the list of dropped
! 		 * attributes to avoid needing to do so in the per-tuple loop.
! 		 */
! 		for (i = 0; i < newTupDesc->natts; i++)
! 		{
! 			if (newTupDesc->attrs[i]->attisdropped)
! 				dropped_attrs = lappend_int(dropped_attrs, i);
! 		}
  
! 		/*
! 		 * Scan through the rows, generating a new row if needed and then
! 		 * checking all the constraints.
! 		 */
! 		scan = heap_beginscan(oldrel, SnapshotNow, 0, NULL);
  
! 		/*
! 		 * Switch to per-tuple memory context and reset it for each tuple
! 		 * produced, so we don't leak memory.
! 		 */
! 		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
  
! 		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
  		{
! 			if (newrel)
! 			{
! 				Oid			tupOid = InvalidOid;
  
! 				/* Extract data from old tuple */
! 				heap_deform_tuple(tuple, oldTupDesc, values, isnull);
! 				if (oldTupDesc->tdhasoid)
! 					tupOid = HeapTupleGetOid(tuple);
  
! 				/* Set dropped attributes to null in new tuple */
! 				foreach(lc, dropped_attrs)
! 					isnull[lfirst_int(lc)] = true;
  
! 				/*
! 				 * Process supplied expressions to replace selected columns.
! 				 * Expression inputs come from the old tuple.
! 				 */
! 				ExecStoreTuple(tuple, oldslot, InvalidBuffer, false);
! 				econtext->ecxt_scantuple = oldslot;
  
! 				foreach(l, tab->newvals)
! 				{
! 					NewColumnValue *ex = lfirst(l);
  
! 					values[ex->attnum - 1] = ExecEvalExpr(ex->exprstate,
! 														  econtext,
! 													 &isnull[ex->attnum - 1],
! 														  NULL);
! 				}
  
! 				/*
! 				 * Form the new tuple. Note that we don't explicitly pfree it,
! 				 * since the per-tuple memory context will be reset shortly.
! 				 */
! 				tuple = heap_form_tuple(newTupDesc, values, isnull);
  
! 				/* Preserve OID, if any */
! 				if (newTupDesc->tdhasoid)
! 					HeapTupleSetOid(tuple, tupOid);
! 			}
  
! 			/* Now check any constraints on the possibly-changed tuple */
! 			ExecStoreTuple(tuple, newslot, InvalidBuffer, false);
! 			econtext->ecxt_scantuple = newslot;
  
! 			foreach(l, notnull_attrs)
! 			{
! 				int			attn = lfirst_int(l);
  
! 				if (heap_attisnull(tuple, attn + 1))
! 					ereport(ERROR,
! 							(errcode(ERRCODE_NOT_NULL_VIOLATION),
! 							 errmsg("column \"%s\" contains null values",
  								NameStr(newTupDesc->attrs[attn]->attname))));
! 			}
  
! 			foreach(l, tab->constraints)
! 			{
! 				NewConstraint *con = lfirst(l);
  
! 				switch (con->contype)
! 				{
! 					case CONSTR_CHECK:
! 						if (!ExecQual(con->qualstate, econtext, true))
! 							ereport(ERROR,
! 									(errcode(ERRCODE_CHECK_VIOLATION),
! 									 errmsg("check constraint \"%s\" is violated by some row",
! 											con->name)));
! 						break;
! 					case CONSTR_FOREIGN:
! 						/* Nothing to do here */
! 						break;
! 					default:
! 						elog(ERROR, "unrecognized constraint type: %d",
! 							 (int) con->contype);
! 				}
  			}
  
! 			/* Write the tuple out to the new relation */
! 			if (newrel)
! 				heap_insert(newrel, tuple, mycid, hi_options, bistate);
  
! 			ResetExprContext(econtext);
  
! 			CHECK_FOR_INTERRUPTS();
! 		}
  
! 		MemoryContextSwitchTo(oldCxt);
! 		heap_endscan(scan);
  
! 		ExecDropSingleTupleTableSlot(oldslot);
! 		ExecDropSingleTupleTableSlot(newslot);
! 	}
  
  	FreeExecutorState(estate);
  
--- 3428,3578 ----
  				!newTupDesc->attrs[i]->attisdropped)
  				notnull_attrs = lappend_int(notnull_attrs, i);
  		}
  	}
  
! 	if (newrel)
! 		ereport(DEBUG1,
! 				(errmsg("Rewriting table \"%s\"",
! 						RelationGetRelationName(oldrel))));
! 	else
! 		ereport(DEBUG1,
! 				(errmsg("Verifying table \"%s\"",
! 						RelationGetRelationName(oldrel))));
  
! 	econtext = GetPerTupleExprContext(estate);
  
! 	/*
! 	 * Make tuple slots for old and new tuples.  Note that even when the
! 	 * tuples are the same, the tupDescs might not be (consider ADD COLUMN
! 	 * without a default).
! 	 */
! 	oldslot = MakeSingleTupleTableSlot(oldTupDesc);
! 	newslot = MakeSingleTupleTableSlot(newTupDesc);
  
! 	/* Preallocate values/isnull arrays */
! 	i = Max(newTupDesc->natts, oldTupDesc->natts);
! 	values = (Datum *) palloc(i * sizeof(Datum));
! 	isnull = (bool *) palloc(i * sizeof(bool));
! 	memset(values, 0, i * sizeof(Datum));
! 	memset(isnull, true, i * sizeof(bool));
  
! 	/*
! 	 * Any attributes that are dropped according to the new tuple
! 	 * descriptor can be set to NULL. We precompute the list of dropped
! 	 * attributes to avoid needing to do so in the per-tuple loop.
! 	 */
! 	for (i = 0; i < newTupDesc->natts; i++)
! 	{
! 		if (newTupDesc->attrs[i]->attisdropped)
! 			dropped_attrs = lappend_int(dropped_attrs, i);
! 	}
  
! 	/*
! 	 * Scan through the rows, generating a new row if needed and then
! 	 * checking all the constraints.
! 	 */
! 	scan = heap_beginscan(oldrel, SnapshotNow, 0, NULL);
  
! 	/*
! 	 * Switch to per-tuple memory context and reset it for each tuple
! 	 * produced, so we don't leak memory.
! 	 */
! 	oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
  
! 	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
! 	{
! 		if (newrel)
  		{
! 			Oid			tupOid = InvalidOid;
  
! 			/* Extract data from old tuple */
! 			heap_deform_tuple(tuple, oldTupDesc, values, isnull);
! 			if (oldTupDesc->tdhasoid)
! 				tupOid = HeapTupleGetOid(tuple);
  
! 			/* Set dropped attributes to null in new tuple */
! 			foreach(lc, dropped_attrs)
! 				isnull[lfirst_int(lc)] = true;
  
! 			/*
! 			 * Process supplied expressions to replace selected columns.
! 			 * Expression inputs come from the old tuple.
! 			 */
! 			ExecStoreTuple(tuple, oldslot, InvalidBuffer, false);
! 			econtext->ecxt_scantuple = oldslot;
  
! 			foreach(l, tab->newvals)
! 			{
! 				NewColumnValue *ex = lfirst(l);
  
! 				values[ex->attnum - 1] = ExecEvalExpr(ex->exprstate,
! 													  econtext,
! 													  &isnull[ex->attnum - 1],
! 													  NULL);
! 			}
  
! 			/*
! 			 * Form the new tuple. Note that we don't explicitly pfree it,
! 			 * since the per-tuple memory context will be reset shortly.
! 			 */
! 			tuple = heap_form_tuple(newTupDesc, values, isnull);
  
! 			/* Preserve OID, if any */
! 			if (newTupDesc->tdhasoid)
! 				HeapTupleSetOid(tuple, tupOid);
! 		}
  
! 		/* Now check any constraints on the possibly-changed tuple */
! 		ExecStoreTuple(tuple, newslot, InvalidBuffer, false);
! 		econtext->ecxt_scantuple = newslot;
  
! 		foreach(l, notnull_attrs)
! 		{
! 			int			attn = lfirst_int(l);
  
! 			if (heap_attisnull(tuple, attn + 1))
! 				ereport(ERROR,
! 						(errcode(ERRCODE_NOT_NULL_VIOLATION),
! 						 errmsg("column \"%s\" contains null values",
  								NameStr(newTupDesc->attrs[attn]->attname))));
! 		}
  
! 		foreach(l, tab->constraints)
! 		{
! 			NewConstraint *con = lfirst(l);
  
! 			switch (con->contype)
! 			{
! 			case CONSTR_CHECK:
! 				if (!ExecQual(con->qualstate, econtext, true))
! 					ereport(ERROR,
! 							(errcode(ERRCODE_CHECK_VIOLATION),
! 							 errmsg("check constraint \"%s\" is violated by some row",
! 									con->name)));
! 				break;
! 			case CONSTR_FOREIGN:
! 				/* Nothing to do here */
! 				break;
! 			default:
! 				elog(ERROR, "unrecognized constraint type: %d",
! 					 (int) con->contype);
  			}
+ 		}
  
! 		/* Write the tuple out to the new relation */
! 		if (newrel)
! 			heap_insert(newrel, tuple, mycid, hi_options, bistate);
  
! 		ResetExprContext(econtext);
  
! 		CHECK_FOR_INTERRUPTS();
! 	}
  
! 	MemoryContextSwitchTo(oldCxt);
! 	heap_endscan(scan);
  
! 	ExecDropSingleTupleTableSlot(oldslot);
! 	ExecDropSingleTupleTableSlot(newslot);
  
  	FreeExecutorState(estate);
  
***************
*** 4271,4276 **** ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
--- 4269,4275 ----
  			newval->expr = defval;
  
  			tab->newvals = lappend(tab->newvals, newval);
+ 			tab->worklevel = WORK_REWRITE;
  		}
  
  		/*
***************
*** 4288,4294 **** ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
  	 */
  	if (isOid)
  	{
! 		tab->new_changeoids = true;
  
  		/* See comments in ATExecDropColumn. */
  		find_composite_type_dependencies(rel->rd_rel->reltype,
--- 4287,4293 ----
  	 */
  	if (isOid)
  	{
! 		tab->worklevel = WORK_REWRITE;
  
  		/* See comments in ATExecDropColumn. */
  		find_composite_type_dependencies(rel->rd_rel->reltype,
***************
*** 4967,4973 **** ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
  		tab = ATGetQueueEntry(wqueue, rel);
  
  		/* Tell Phase 3 to physically remove the OID column */
! 		tab->new_changeoids = true;
  
  		/*
  		 * We would also need to rewrite all tables using this table's rowtype
--- 4966,4972 ----
  		tab = ATGetQueueEntry(wqueue, rel);
  
  		/* Tell Phase 3 to physically remove the OID column */
! 		tab->worklevel = WORK_REWRITE;
  
  		/*
  		 * We would also need to rewrite all tables using this table's rowtype
***************
*** 5000,5006 **** ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
  	/* suppress schema rights check when rebuilding existing index */
  	check_rights = !is_rebuild;
  	/* skip index build if phase 3 will have to rewrite table anyway */
! 	skip_build = (tab->newvals != NIL);
  	/* suppress notices when rebuilding existing index */
  	quiet = is_rebuild;
  
--- 4999,5005 ----
  	/* suppress schema rights check when rebuilding existing index */
  	check_rights = !is_rebuild;
  	/* skip index build if phase 3 will have to rewrite table anyway */
! 	skip_build = (tab->worklevel == WORK_REWRITE);
  	/* suppress notices when rebuilding existing index */
  	quiet = is_rebuild;
  
***************
*** 5139,5144 **** ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 5138,5144 ----
  		newcon->qual = (Node *) make_ands_implicit((Expr *) ccon->expr);
  
  		tab->constraints = lappend(tab->constraints, newcon);
+ 		tab->worklevel = Max(tab->worklevel, WORK_SCAN);
  
  		/* Save the actually assigned name if it was defaulted */
  		if (constr->conname == NULL)
***************
*** 5485,5490 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
--- 5485,5494 ----
  		newcon->qual = (Node *) fkconstraint;
  
  		tab->constraints = lappend(tab->constraints, newcon);
+ 		/*
+ 		 * No need to set tab->worklevel; foreign key validation is a distinct
+ 		 * aspect of Phase 3.
+ 		 */
  	}
  
  	/*
***************
*** 6369,6374 **** ATPrepAlterColumnType(List **wqueue,
--- 6373,6379 ----
  		newval->expr = (Expr *) transform;
  
  		tab->newvals = lappend(tab->newvals, newval);
+ 		tab->worklevel = WORK_REWRITE;
  
  		/*
  		 * If we need to rewrite or scan this table, tables using its rowtype as