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.
	 */
#ifdef REDUCED_ALTER_TABLE_LOCK_LEVELS
	ListCell   *lcmd;
	LOCKMODE	lockmode = ShareUpdateExclusiveLock;

	foreach(lcmd, cmds)
	{
		AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
		LOCKMODE	cmd_lockmode = AccessExclusiveLock; /* default for compiler */

		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.
				 */
			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_AlterColumnType:	/* must rewrite heap */
			case AT_DropConstraint:		/* as DROP INDEX */
			case AT_AddOids:	/* must rewrite heap */
			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_DisableRule:		/* may change SELECT rules */
			case AT_ChangeOwner:		/* change visible to SELECT */
			case AT_SetTableSpace:		/* must rewrite heap */
			case AT_DropNotNull:		/* may change some SQL plans */
			case AT_SetNotNull:
			case AT_GenericOptions:
			case AT_AlterColumnGenericOptions:
				cmd_lockmode = AccessExclusiveLock;
				break;

				/*
				 * These subcommands affect write operations only.
				 */
			case AT_ColumnDefault:
			case AT_ProcessedConstraint:		/* becomes AT_AddConstraint */
			case AT_AddConstraintRecurse:		/* becomes AT_AddConstraint */
			case AT_ReAddConstraint:	/* becomes AT_AddConstraint */
			case AT_EnableTrig:
			case AT_EnableAlwaysTrig:
			case AT_EnableReplicaTrig:
			case AT_EnableTrigAll:
			case AT_EnableTrigUser:
			case AT_DisableTrig:
			case AT_DisableTrigAll:
			case AT_DisableTrigUser:
			case AT_AddIndex:	/* from ADD CONSTRAINT */
			case AT_AddIndexConstraint:
			case AT_ReplicaIdentity:
				cmd_lockmode = ShareRowExclusiveLock;
				break;

			case AT_AddConstraint:
				if (IsA(cmd->def, Constraint))
				{
					Constraint *con = (Constraint *) cmd->def;

					switch (con->contype)
					{
						case CONSTR_EXCLUSION:
						case CONSTR_PRIMARY:
						case CONSTR_UNIQUE:

							/*
							 * Cases essentially the same as CREATE INDEX. We
							 * could reduce the lock strength to ShareLock if
							 * we can work out how to allow concurrent catalog
							 * updates.
							 */
							cmd_lockmode = ShareRowExclusiveLock;
							break;
						case CONSTR_FOREIGN:

							/*
							 * 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.
							 */
							cmd_lockmode = ShareRowExclusiveLock;
							break;

						default:
							cmd_lockmode = ShareRowExclusiveLock;
					}
				}
				break;

				/*
				 * These subcommands affect inheritance behaviour. Queries
				 * started before us will continue to see the old inheritance
				 * 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.
				 */
			case AT_AddInherit:
			case AT_DropInherit:
				cmd_lockmode = ShareUpdateExclusiveLock;
				break;

				/*
				 * 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.  Do avoid concurrent pg_class updates,
				 * though.
				 */
			case AT_AddOf:
			case AT_DropOf:
				cmd_lockmode = ShareUpdateExclusiveLock;

				/*
				 * These subcommands affect general strategies for performance
				 * and maintenance, though don't change the semantic results
				 * from normal data reads and writes. Delaying an ALTER TABLE
				 * behind currently active writes only delays the point where
				 * the new strategy begins to take effect, so there is no
				 * benefit in waiting. In this case the minimum restriction
				 * 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:
				cmd_lockmode = ShareUpdateExclusiveLock;
				break;

			default:			/* oops */
				elog(ERROR, "unrecognized alter table type: %d",
					 (int) cmd->subtype);
				break;
		}

		/*
		 * Take the greatest lockmode from any subcommand
		 */
		if (cmd_lockmode > lockmode)
			lockmode = cmd_lockmode;
	}
#else
	LOCKMODE	lockmode = AccessExclusiveLock;
#endif

	return lockmode;
}