diff --git a/contrib/pg_upgrade/function.c b/contrib/pg_upgrade/function.c
index 31255d637dc8a6fd2642650bccfec972401f188f..c76aaeb090bde61c4308807033164946f2aa22cc 100644
--- a/contrib/pg_upgrade/function.c
+++ b/contrib/pg_upgrade/function.c
@@ -79,7 +79,7 @@ install_support_functions(void)
 								  "LANGUAGE C STRICT;"));
 		PQclear(executeQueryOrDie(conn,
 								  "CREATE OR REPLACE FUNCTION "
-			 "		binary_upgrade.add_pg_enum_label(OID, OID, NAME) "
+			 "		binary_upgrade.set_next_pg_enum_oid(OID) "
 								  "RETURNS VOID "
 								  "AS '$libdir/pg_upgrade_support' "
 								  "LANGUAGE C STRICT;"));
diff --git a/contrib/pg_upgrade_support/pg_upgrade_support.c b/contrib/pg_upgrade_support/pg_upgrade_support.c
index c956be187ac398495944d3f0b8c07ec18a970040..3ec436fe140c2effcc5793e2c39cb6d6949dc975 100644
--- a/contrib/pg_upgrade_support/pg_upgrade_support.c
+++ b/contrib/pg_upgrade_support/pg_upgrade_support.c
@@ -16,13 +16,6 @@
 
 /* THIS IS USED ONLY FOR PG >= 9.0 */
 
-/*
- * Cannot include "catalog/pg_enum.h" here because we might
- * not be compiling against PG 9.0.
- */
-extern void EnumValuesCreate(Oid enumTypeOid, List *vals,
-				 Oid binary_upgrade_next_pg_enum_oid);
-
 #ifdef PG_MODULE_MAGIC
 PG_MODULE_MAGIC;
 #endif
@@ -33,6 +26,7 @@ extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_toast_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_heap_relfilenode;
 extern PGDLLIMPORT Oid binary_upgrade_next_toast_relfilenode;
 extern PGDLLIMPORT Oid binary_upgrade_next_index_relfilenode;
+extern PGDLLIMPORT Oid binary_upgrade_next_pg_enum_oid;
 
 Datum		set_next_pg_type_oid(PG_FUNCTION_ARGS);
 Datum		set_next_pg_type_array_oid(PG_FUNCTION_ARGS);
@@ -40,7 +34,7 @@ Datum		set_next_pg_type_toast_oid(PG_FUNCTION_ARGS);
 Datum		set_next_heap_relfilenode(PG_FUNCTION_ARGS);
 Datum		set_next_toast_relfilenode(PG_FUNCTION_ARGS);
 Datum		set_next_index_relfilenode(PG_FUNCTION_ARGS);
-Datum		add_pg_enum_label(PG_FUNCTION_ARGS);
+Datum		set_next_pg_enum_oid(PG_FUNCTION_ARGS);
 
 PG_FUNCTION_INFO_V1(set_next_pg_type_oid);
 PG_FUNCTION_INFO_V1(set_next_pg_type_array_oid);
@@ -48,7 +42,7 @@ PG_FUNCTION_INFO_V1(set_next_pg_type_toast_oid);
 PG_FUNCTION_INFO_V1(set_next_heap_relfilenode);
 PG_FUNCTION_INFO_V1(set_next_toast_relfilenode);
 PG_FUNCTION_INFO_V1(set_next_index_relfilenode);
-PG_FUNCTION_INFO_V1(add_pg_enum_label);
+PG_FUNCTION_INFO_V1(set_next_pg_enum_oid);
 
 Datum
 set_next_pg_type_oid(PG_FUNCTION_ARGS)
@@ -111,14 +105,11 @@ set_next_index_relfilenode(PG_FUNCTION_ARGS)
 }
 
 Datum
-add_pg_enum_label(PG_FUNCTION_ARGS)
+set_next_pg_enum_oid(PG_FUNCTION_ARGS)
 {
 	Oid			enumoid = PG_GETARG_OID(0);
-	Oid			typoid = PG_GETARG_OID(1);
-	Name		label = PG_GETARG_NAME(2);
 
-	EnumValuesCreate(typoid, list_make1(makeString(NameStr(*label))),
-					 enumoid);
+	binary_upgrade_next_pg_enum_oid = enumoid;
 
 	PG_RETURN_VOID();
 }
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index b7b48e4fb93c26c421cea5c6fd01a914f9ccc8b5..9a8729b8b3194a780ea4b15dd5cfcd8bff297543 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2623,12 +2623,9 @@
 
   <para>
    The <structname>pg_enum</structname> catalog contains entries
-   matching enum types to their associated values and labels. The
+   showing the values and labels for each enum type. The
    internal representation of a given enum value is actually the OID
-   of its associated row in <structname>pg_enum</structname>.  The
-   OIDs for a particular enum type are guaranteed to be ordered in
-   the way the type should sort, but there is no guarantee about the
-   ordering of OIDs of unrelated enum types.
+   of its associated row in <structname>pg_enum</structname>.
   </para>
 
   <table>
@@ -2652,6 +2649,13 @@
       <entry>The OID of the <structname>pg_type</> entry owning this enum value</entry>
      </row>
 
+     <row>
+      <entry><structfield>enumsortorder</structfield></entry>
+      <entry><type>float4</type></entry>
+      <entry></entry>
+      <entry>The sort position of this enum value within its enum type</entry>
+     </row>
+
      <row>
       <entry><structfield>enumlabel</structfield></entry>
       <entry><type>name</type></entry>
@@ -2661,6 +2665,26 @@
     </tbody>
    </tgroup>
   </table>
+
+  <para>
+   The OIDs for <structname>pg_enum</structname> rows follow a special
+   rule: even-numbered OIDs are guaranteed to be ordered in the same way
+   as the sort ordering of their enum type.  That is, if two even OIDs
+   belong to the same enum type, the smaller OID must have the smaller
+   <structfield>enumsortorder</structfield> value.  Odd-numbered OID values
+   need bear no relationship to the sort order.  This rule allows the
+   enum comparison routines to avoid catalog lookups in many common cases.
+   The routines that create and alter enum types attempt to assign even
+   OIDs to enum values whenever possible.
+  </para>
+
+  <para>
+   When an enum type is created, its members are assigned sort-order
+   positions 1..<replaceable>n</>.  But members added later might be given
+   negative or fractional values of <structfield>enumsortorder</structfield>.
+   The only requirement on these values is that they be correctly
+   ordered and unique within each enum type.
+  </para>
  </sect1>
 
 
diff --git a/doc/src/sgml/ref/alter_type.sgml b/doc/src/sgml/ref/alter_type.sgml
index 315922ea8365c482cdd787d1a775e0220ba1625c..90de2e81ef8e8b2760e733194bb67d334c4ab8b4 100644
--- a/doc/src/sgml/ref/alter_type.sgml
+++ b/doc/src/sgml/ref/alter_type.sgml
@@ -24,10 +24,11 @@ PostgreSQL documentation
  <refsynopsisdiv>
 <synopsis>
 ALTER TYPE <replaceable class="PARAMETER">name</replaceable> <replaceable class="PARAMETER">action</replaceable> [, ... ]
-ALTER TYPE <replaceable class="PARAMETER">name</replaceable> OWNER TO <replaceable class="PARAMETER">new_owner</replaceable> 
+ALTER TYPE <replaceable class="PARAMETER">name</replaceable> OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
 ALTER TYPE <replaceable class="PARAMETER">name</replaceable> RENAME ATTRIBUTE <replaceable class="PARAMETER">attribute_name</replaceable> TO <replaceable class="PARAMETER">new_attribute_name</replaceable>
 ALTER TYPE <replaceable class="PARAMETER">name</replaceable> RENAME TO <replaceable class="PARAMETER">new_name</replaceable>
 ALTER TYPE <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <replaceable class="PARAMETER">new_schema</replaceable>
+ALTER TYPE <replaceable class="PARAMETER">name</replaceable> ADD <replaceable class="PARAMETER">new_enum_value</replaceable> [ { BEFORE | AFTER } <replaceable class="PARAMETER">existing_enum_value</replaceable> ]
 
 <phrase>where <replaceable class="PARAMETER">action</replaceable> is one of:</phrase>
 
@@ -103,6 +104,18 @@ ALTER TYPE <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <replace
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><literal>ADD [ BEFORE | AFTER ]</literal></term>
+    <listitem>
+     <para>
+      This form adds a new value to an enum type. If the new value's place in
+      the enum's ordering is not specified using <literal>BEFORE</literal> or
+      <literal>AFTER</literal>, then the new item is placed at the end of the
+      list of values.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
   </para>
 
@@ -181,7 +194,7 @@ ALTER TYPE <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <replace
       <term><replaceable class="PARAMETER">new_attribute_name</replaceable></term>
       <listitem>
        <para>
-        The new name of the attribute begin renamed.
+        The new name of the attribute to be renamed.
        </para>
       </listitem>
      </varlistentry>
@@ -196,10 +209,53 @@ ALTER TYPE <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <replace
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">new_enum_value</replaceable></term>
+      <listitem>
+       <para>
+        The new value to be added to an enum type's list of values.
+        Like all enum literals, it needs to be quoted.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><replaceable class="PARAMETER">existing_enum_value</replaceable></term>
+      <listitem>
+       <para>
+        The existing enum value that the new value should be added immediately
+        before or after in the enum type's sort ordering.
+        Like all enum literals, it needs to be quoted.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
    </para>
   </refsect1>
 
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   <command>ALTER TYPE ... ADD</> (the form that adds a new value to an
+   enum type) cannot be executed inside a transaction block.
+  </para>
+
+  <para>
+   Comparisons involving an added enum value will sometimes be slower than
+   comparisons involving only original members of the enum type.  This will
+   usually only occur if <literal>BEFORE</literal> or <literal>AFTER</literal>
+   is used to set the new value's sort position somewhere other than at the
+   end of the list.  However, sometimes it will happen even though the new
+   value is added at the end (this occurs if the OID counter <quote>wrapped
+   around</> since the original creation of the enum type).  The slowdown is
+   usually insignificant; but if it matters, optimal performance can be
+   regained by dropping and recreating the enum type, or by dumping and
+   reloading the database.
+  </para>
+ </refsect1>
+
  <refsect1>
   <title>Examples</title>
 
@@ -230,6 +286,13 @@ ALTER TYPE email SET SCHEMA customers;
    To add a new attribute to a type:
 <programlisting>
 ALTER TYPE compfoo ADD ATTRIBUTE f3 int;
+</programlisting>
+  </para>
+
+  <para>
+   To add a new value to an enum type in a particular sort position:
+<programlisting>
+ALTER TYPE colors ADD 'orange' AFTER 'red';
 </programlisting>
   </para>
  </refsect1>
diff --git a/src/backend/catalog/pg_enum.c b/src/backend/catalog/pg_enum.c
index d544c1f4773d322b88d5c07bb192d6f136629829..0c384def7b60e17a0c0dc03bce0fc83d23d9389e 100644
--- a/src/backend/catalog/pg_enum.c
+++ b/src/backend/catalog/pg_enum.c
@@ -15,15 +15,24 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/xact.h"
 #include "catalog/catalog.h"
 #include "catalog/indexing.h"
 #include "catalog/pg_enum.h"
+#include "catalog/pg_type.h"
+#include "storage/lmgr.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/rel.h"
+#include "utils/syscache.h"
 #include "utils/tqual.h"
 
+
+Oid      binary_upgrade_next_pg_enum_oid = InvalidOid;
+
+static void RenumberEnumType(Relation pg_enum, HeapTuple *existing, int nelems);
 static int	oid_cmp(const void *p1, const void *p2);
+static int	sort_order_cmp(const void *p1, const void *p2);
 
 
 /*
@@ -33,11 +42,9 @@ static int	oid_cmp(const void *p1, const void *p2);
  * vals is a list of Value strings.
  */
 void
-EnumValuesCreate(Oid enumTypeOid, List *vals,
-				 Oid binary_upgrade_next_pg_enum_oid)
+EnumValuesCreate(Oid enumTypeOid, List *vals)
 {
 	Relation	pg_enum;
-	TupleDesc	tupDesc;
 	NameData	enumlabel;
 	Oid		   *oids;
 	int			elemno,
@@ -50,48 +57,42 @@ EnumValuesCreate(Oid enumTypeOid, List *vals,
 	num_elems = list_length(vals);
 
 	/*
-	 * XXX we do not bother to check the list of values for duplicates --- if
+	 * We do not bother to check the list of values for duplicates --- if
 	 * you have any, you'll get a less-than-friendly unique-index violation.
-	 * Is it worth trying harder?
+	 * It is probably not worth trying harder.
 	 */
 
 	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
-	tupDesc = pg_enum->rd_att;
 
 	/*
-	 * Allocate oids
+	 * Allocate OIDs for the enum's members.
+	 *
+	 * While this method does not absolutely guarantee that we generate no
+	 * duplicate OIDs (since we haven't entered each oid into the table
+	 * before allocating the next), trouble could only occur if the OID
+	 * counter wraps all the way around before we finish. Which seems
+	 * unlikely.
 	 */
 	oids = (Oid *) palloc(num_elems * sizeof(Oid));
-	if (OidIsValid(binary_upgrade_next_pg_enum_oid))
-	{
-		if (num_elems != 1)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-					 errmsg("EnumValuesCreate() can only set a single OID")));
-		oids[0] = binary_upgrade_next_pg_enum_oid;
-		binary_upgrade_next_pg_enum_oid = InvalidOid;
-	}
-	else
+
+	for (elemno = 0; elemno < num_elems; elemno++)
 	{
 		/*
-		 * While this method does not absolutely guarantee that we generate no
-		 * duplicate oids (since we haven't entered each oid into the table
-		 * before allocating the next), trouble could only occur if the oid
-		 * counter wraps all the way around before we finish. Which seems
-		 * unlikely.
+		 * We assign even-numbered OIDs to all the new enum labels.  This
+		 * tells the comparison functions the OIDs are in the correct sort
+		 * order and can be compared directly.
 		 */
-		for (elemno = 0; elemno < num_elems; elemno++)
-		{
-			/*
-			 * The pg_enum.oid is stored in user tables.  This oid must be
-			 * preserved by binary upgrades.
-			 */
-			oids[elemno] = GetNewOid(pg_enum);
-		}
-		/* sort them, just in case counter wrapped from high to low */
-		qsort(oids, num_elems, sizeof(Oid), oid_cmp);
+		Oid		new_oid;
+
+		do {
+			new_oid = GetNewOid(pg_enum);
+		} while (new_oid & 1);
+		oids[elemno] = new_oid;
 	}
 
+	/* sort them, just in case OID counter wrapped from high to low */
+	qsort(oids, num_elems, sizeof(Oid), oid_cmp);
+
 	/* and make the entries */
 	memset(nulls, false, sizeof(nulls));
 
@@ -112,10 +113,11 @@ EnumValuesCreate(Oid enumTypeOid, List *vals,
 							   NAMEDATALEN - 1)));
 
 		values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
+		values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(elemno + 1);
 		namestrcpy(&enumlabel, lab);
 		values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
 
-		tup = heap_form_tuple(tupDesc, values, nulls);
+		tup = heap_form_tuple(RelationGetDescr(pg_enum), values, nulls);
 		HeapTupleSetOid(tup, oids[elemno]);
 
 		simple_heap_insert(pg_enum, tup);
@@ -164,7 +166,322 @@ EnumValuesDelete(Oid enumTypeOid)
 }
 
 
-/* qsort comparison function */
+/*
+ * AddEnumLabel
+ *		Add a new label to the enum set. By default it goes at
+ *		the end, but the user can choose to place it before or
+ *		after any existing set member.
+ */
+void
+AddEnumLabel(Oid enumTypeOid,
+			 const char *newVal,
+			 const char *neighbor,
+			 bool newValIsAfter)
+{
+	Relation	pg_enum;
+	Oid			newOid;
+	Datum		values[Natts_pg_enum];
+	bool		nulls[Natts_pg_enum];
+	NameData	enumlabel;
+	HeapTuple	enum_tup;
+	float4		newelemorder;
+	HeapTuple  *existing;
+	CatCList   *list;
+	int			nelems;
+	int			i;
+
+	/* check length of new label is ok */
+	if (strlen(newVal) > (NAMEDATALEN - 1))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_NAME),
+				 errmsg("invalid enum label \"%s\"", newVal),
+				 errdetail("Labels must be %d characters or less.",
+						   NAMEDATALEN - 1)));
+
+	/*
+	 * Acquire a lock on the enum type, which we won't release until commit.
+	 * This ensures that two backends aren't concurrently modifying the same
+	 * enum type.  Without that, we couldn't be sure to get a consistent
+	 * view of the enum members via the syscache.  Note that this does not
+	 * block other backends from inspecting the type; see comments for
+	 * RenumberEnumType.
+	 */
+	LockDatabaseObject(TypeRelationId, enumTypeOid, 0, ExclusiveLock);
+
+	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
+
+	/* If we have to renumber the existing members, we restart from here */
+restart:
+
+	/* Get the list of existing members of the enum */
+	list = SearchSysCacheList1(ENUMTYPOIDNAME,
+							   ObjectIdGetDatum(enumTypeOid));
+	nelems =  list->n_members;
+
+	/* Sort the existing members by enumsortorder */
+	existing = (HeapTuple *) palloc(nelems * sizeof(HeapTuple));
+	for (i = 0; i < nelems; i++)
+		existing[i] = &(list->members[i]->tuple);
+
+	qsort(existing, nelems, sizeof(HeapTuple), sort_order_cmp);
+
+	if (neighbor == NULL)
+	{
+		/*
+		 * Put the new label at the end of the list.
+		 * No change to existing tuples is required.
+		 */
+		if (nelems > 0)
+		{
+			Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nelems - 1]);
+
+			newelemorder = en->enumsortorder + 1;
+		}
+		else
+			newelemorder = 1;
+	}
+	else
+	{
+		/* BEFORE or AFTER was specified */
+		int				nbr_index;
+		int				other_nbr_index;
+		Form_pg_enum	nbr_en;
+		Form_pg_enum	other_nbr_en;
+
+		/* Locate the neighbor element */
+		for (nbr_index = 0; nbr_index < nelems; nbr_index++)
+		{
+			Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nbr_index]);
+
+			if (strcmp(NameStr(en->enumlabel), neighbor) == 0)
+				break;
+		}
+		if (nbr_index >= nelems)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("\"%s\" is not an existing enum label",
+							neighbor)));
+		nbr_en = (Form_pg_enum) GETSTRUCT(existing[nbr_index]);
+
+		/*
+		 * Attempt to assign an appropriate enumsortorder value: one less
+		 * than the smallest member, one more than the largest member,
+		 * or halfway between two existing members.
+		 *
+		 * In the "halfway" case, because of the finite precision of float4,
+		 * we might compute a value that's actually equal to one or the
+		 * other of its neighbors.  In that case we renumber the existing
+		 * members and try again.
+		 */
+		if (newValIsAfter)
+			other_nbr_index = nbr_index + 1;
+		else
+			other_nbr_index = nbr_index - 1;
+
+		if (other_nbr_index < 0)
+			newelemorder = nbr_en->enumsortorder - 1;
+		else if (other_nbr_index >= nelems)
+			newelemorder = nbr_en->enumsortorder + 1;
+		else
+		{
+			other_nbr_en = (Form_pg_enum) GETSTRUCT(existing[other_nbr_index]);
+			newelemorder = (nbr_en->enumsortorder +
+							other_nbr_en->enumsortorder) / 2;
+			if (newelemorder == nbr_en->enumsortorder ||
+				newelemorder == other_nbr_en->enumsortorder)
+			{
+				RenumberEnumType(pg_enum, existing, nelems);
+				/* Clean up and start over */
+				pfree(existing);
+				ReleaseCatCacheList(list);
+				goto restart;
+			}
+		}
+	}
+
+	/* Get a new OID for the new label */
+	if (OidIsValid(binary_upgrade_next_pg_enum_oid))
+	{
+		/*
+		 * In binary upgrades, just add the new label with the predetermined
+		 * Oid.  It's pg_upgrade's responsibility that the Oid meets
+		 * requirements.
+		 */
+		if (neighbor != NULL)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("ALTER TYPE ADD BEFORE/AFTER is incompatible with binary upgrade")));
+
+		newOid = binary_upgrade_next_pg_enum_oid;
+		binary_upgrade_next_pg_enum_oid = InvalidOid;
+	}
+	else
+	{
+		/*
+		 * Normal case: we need to allocate a new Oid for the value.
+		 *
+		 * We want to give the new element an even-numbered Oid if it's safe,
+		 * which is to say it compares correctly to all pre-existing even
+		 * numbered Oids in the enum.  Otherwise, we must give it an odd Oid.
+		 */
+		for (;;)
+		{
+			bool	sorts_ok;
+
+			/* Get a new OID (different from all existing pg_enum tuples) */
+			newOid = GetNewOid(pg_enum);
+
+			/*
+			 * Detect whether it sorts correctly relative to existing
+			 * even-numbered labels of the enum.  We can ignore existing
+			 * labels with odd Oids, since a comparison involving one of
+			 * those will not take the fast path anyway.
+			 */
+			sorts_ok = true;
+			for (i = 0; i < nelems; i++)
+			{
+				HeapTuple	exists_tup = existing[i];
+				Form_pg_enum exists_en = (Form_pg_enum) GETSTRUCT(exists_tup);
+				Oid			exists_oid = HeapTupleGetOid(exists_tup);
+
+				if (exists_oid & 1)
+					continue;	/* ignore odd Oids */
+
+				if (exists_en->enumsortorder < newelemorder)
+				{
+					/* should sort before */
+					if (exists_oid >= newOid)
+					{
+						sorts_ok = false;
+						break;
+					}
+				}
+				else
+				{
+					/* should sort after */
+					if (exists_oid <= newOid)
+					{
+						sorts_ok = false;
+						break;
+					}
+				}
+			}
+
+			if (sorts_ok)
+			{
+				/* If it's even and sorts OK, we're done. */
+				if ((newOid & 1) == 0)
+					break;
+
+				/*
+				 * If it's odd, and sorts OK, loop back to get another OID
+				 * and try again.  Probably, the next available even OID
+				 * will sort correctly too, so it's worth trying.
+				 */
+			}
+			else
+			{
+				/*
+				 * If it's odd, and does not sort correctly, we're done.
+				 * (Probably, the next available even OID would sort
+				 * incorrectly too, so no point in trying again.)
+				 */
+				if (newOid & 1)
+					break;
+
+				/*
+				 * If it's even, and does not sort correctly, loop back to get
+				 * another OID and try again.  (We *must* reject this case.)
+				 */
+			}
+		}
+	}
+
+	/* Done with info about existing members */
+	pfree(existing);
+	ReleaseCatCacheList(list);
+
+	/* Create the new pg_enum entry */
+	memset(nulls, false, sizeof(nulls));
+	values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
+	values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(newelemorder);
+	namestrcpy(&enumlabel, newVal);
+	values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
+	enum_tup = heap_form_tuple(RelationGetDescr(pg_enum), values, nulls);
+	HeapTupleSetOid(enum_tup, newOid);
+	simple_heap_insert(pg_enum, enum_tup);
+	CatalogUpdateIndexes(pg_enum, enum_tup);
+	heap_freetuple(enum_tup);
+
+	heap_close(pg_enum, RowExclusiveLock);
+}
+
+
+/*
+ * RenumberEnumType
+ *		Renumber existing enum elements to have sort positions 1..n.
+ *
+ * We avoid doing this unless absolutely necessary; in most installations
+ * it will never happen.  The reason is that updating existing pg_enum
+ * entries creates hazards for other backends that are concurrently reading
+ * pg_enum with SnapshotNow semantics.  A concurrent SnapshotNow scan could
+ * see both old and new versions of an updated row as valid, or neither of
+ * them, if the commit happens between scanning the two versions.  It's
+ * also quite likely for a concurrent scan to see an inconsistent set of
+ * rows (some members updated, some not).
+ *
+ * We can avoid these risks by reading pg_enum with an MVCC snapshot
+ * instead of SnapshotNow, but that forecloses use of the syscaches.
+ * We therefore make the following choices:
+ *
+ * 1. Any code that is interested in the enumsortorder values MUST read
+ * pg_enum with an MVCC snapshot, or else acquire lock on the enum type
+ * to prevent concurrent execution of AddEnumLabel().  The risk of
+ * seeing inconsistent values of enumsortorder is too high otherwise.
+ *
+ * 2. Code that is not examining enumsortorder can use a syscache
+ * (for example, enum_in and enum_out do so).  The worst that can happen
+ * is a transient failure to find any valid value of the row.  This is
+ * judged acceptable in view of the infrequency of use of RenumberEnumType.
+ */
+static void
+RenumberEnumType(Relation pg_enum, HeapTuple *existing, int nelems)
+{
+	int			i;
+
+	/*
+	 * We should only need to increase existing elements' enumsortorders,
+	 * never decrease them.  Therefore, work from the end backwards, to avoid
+	 * unwanted uniqueness violations.
+	 */
+	for (i = nelems - 1; i >= 0; i--)
+	{
+		HeapTuple	newtup;
+		Form_pg_enum en;
+		float4		newsortorder;
+
+		newtup = heap_copytuple(existing[i]);
+		en = (Form_pg_enum) GETSTRUCT(newtup);
+
+		newsortorder = i + 1;
+		if (en->enumsortorder != newsortorder)
+		{
+			en->enumsortorder = newsortorder;
+
+			simple_heap_update(pg_enum, &newtup->t_self, newtup);
+
+			CatalogUpdateIndexes(pg_enum, newtup);
+		}
+
+		heap_freetuple(newtup);
+	}
+
+	/* Make the updates visible */
+	CommandCounterIncrement();
+}
+
+
+/* qsort comparison function for oids */
 static int
 oid_cmp(const void *p1, const void *p2)
 {
@@ -177,3 +494,20 @@ oid_cmp(const void *p1, const void *p2)
 		return 1;
 	return 0;
 }
+
+/* qsort comparison function for tuples by sort order */
+static int
+sort_order_cmp(const void *p1, const void *p2)
+{
+	HeapTuple		v1 = *((const HeapTuple *) p1);
+	HeapTuple		v2 = *((const HeapTuple *) p2);
+	Form_pg_enum	en1 = (Form_pg_enum) GETSTRUCT(v1);
+	Form_pg_enum	en2 = (Form_pg_enum) GETSTRUCT(v2);
+
+	if (en1->enumsortorder < en2->enumsortorder)
+		return -1;
+	else if (en1->enumsortorder > en2->enumsortorder)
+		return 1;
+	else
+		return 0;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 46b156e09a30ff264e6cf2e7567a2beb8cda43cb..220be9b443b54143f4ed097a2786f40aaf66e415 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -84,7 +84,8 @@ static Oid	findTypeTypmodinFunction(List *procname);
 static Oid	findTypeTypmodoutFunction(List *procname);
 static Oid	findTypeAnalyzeFunction(List *procname, Oid typeOid);
 static List *get_rels_with_domain(Oid domainOid, LOCKMODE lockmode);
-static void checkDomainOwner(HeapTuple tup, TypeName *typename);
+static void checkDomainOwner(HeapTuple tup);
+static void checkEnumOwner(HeapTuple tup);
 static char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
 					Oid baseTypeOid,
 					int typMod, Constraint *constr,
@@ -1150,7 +1151,7 @@ DefineEnum(CreateEnumStmt *stmt)
 				   false);		/* Type NOT NULL */
 
 	/* Enter the enum's values into pg_enum */
-	EnumValuesCreate(enumTypeOid, stmt->vals, InvalidOid);
+	EnumValuesCreate(enumTypeOid, stmt->vals);
 
 	/*
 	 * Create the array type that goes with it.
@@ -1191,6 +1192,60 @@ DefineEnum(CreateEnumStmt *stmt)
 	pfree(enumArrayName);
 }
 
+/*
+ * AlterEnum
+ *		Adds a new label to an existing enum.
+ */
+void
+AlterEnum(AlterEnumStmt *stmt)
+{
+	Oid			enum_type_oid;
+	TypeName   *typename;
+	HeapTuple	tup;
+
+	/* Make a TypeName so we can use standard type lookup machinery */
+	typename = makeTypeNameFromNameList(stmt->typeName);
+	enum_type_oid = typenameTypeId(NULL, typename, NULL);
+
+	tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(enum_type_oid));
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for type %u", enum_type_oid);
+
+	/* Check it's an enum and check user has permission to ALTER the enum */
+	checkEnumOwner(tup);
+
+	/* Add the new label */
+	AddEnumLabel(enum_type_oid, stmt->newVal,
+				 stmt->newValNeighbor, stmt->newValIsAfter);
+
+	ReleaseSysCache(tup);
+}
+
+
+/*
+ * checkEnumOwner
+ *
+ * Check that the type is actually an enum and that the current user
+ * has permission to do ALTER TYPE on it.  Throw an error if not.
+ */
+static void
+checkEnumOwner(HeapTuple tup)
+{
+	Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup);
+
+	/* Check that this is actually an enum */
+	if (typTup->typtype != TYPTYPE_ENUM)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("%s is not an enum",
+						format_type_be(HeapTupleGetOid(tup)))));
+
+	/* Permission check: must own type */
+	if (!pg_type_ownercheck(HeapTupleGetOid(tup), GetUserId()))
+		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE,
+					   format_type_be(HeapTupleGetOid(tup)));
+}
+
 
 /*
  * Find suitable I/O functions for a type.
@@ -1576,7 +1631,7 @@ AlterDomainDefault(List *names, Node *defaultRaw)
 	typTup = (Form_pg_type) GETSTRUCT(tup);
 
 	/* Check it's a domain and check user has permission for ALTER DOMAIN */
-	checkDomainOwner(tup, typename);
+	checkDomainOwner(tup);
 
 	/* Setup new tuple */
 	MemSet(new_record, (Datum) 0, sizeof(new_record));
@@ -1702,7 +1757,7 @@ AlterDomainNotNull(List *names, bool notNull)
 	typTup = (Form_pg_type) GETSTRUCT(tup);
 
 	/* Check it's a domain and check user has permission for ALTER DOMAIN */
-	checkDomainOwner(tup, typename);
+	checkDomainOwner(tup);
 
 	/* Is the domain already set to the desired constraint? */
 	if (typTup->typnotnull == notNull)
@@ -1801,7 +1856,7 @@ AlterDomainDropConstraint(List *names, const char *constrName,
 		elog(ERROR, "cache lookup failed for type %u", domainoid);
 
 	/* Check it's a domain and check user has permission for ALTER DOMAIN */
-	checkDomainOwner(tup, typename);
+	checkDomainOwner(tup);
 
 	/* Grab an appropriate lock on the pg_constraint relation */
 	conrel = heap_open(ConstraintRelationId, RowExclusiveLock);
@@ -1875,7 +1930,7 @@ AlterDomainAddConstraint(List *names, Node *newConstraint)
 	typTup = (Form_pg_type) GETSTRUCT(tup);
 
 	/* Check it's a domain and check user has permission for ALTER DOMAIN */
-	checkDomainOwner(tup, typename);
+	checkDomainOwner(tup);
 
 	if (!IsA(newConstraint, Constraint))
 		elog(ERROR, "unrecognized node type: %d",
@@ -2187,7 +2242,7 @@ get_rels_with_domain(Oid domainOid, LOCKMODE lockmode)
  * has permission to do ALTER DOMAIN on it.  Throw an error if not.
  */
 static void
-checkDomainOwner(HeapTuple tup, TypeName *typename)
+checkDomainOwner(HeapTuple tup)
 {
 	Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup);
 
@@ -2195,8 +2250,8 @@ checkDomainOwner(HeapTuple tup, TypeName *typename)
 	if (typTup->typtype != TYPTYPE_DOMAIN)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a domain",
-						TypeNameToString(typename))));
+				 errmsg("%s is not a domain",
+						format_type_be(HeapTupleGetOid(tup)))));
 
 	/* Permission check: must own type */
 	if (!pg_type_ownercheck(HeapTupleGetOid(tup), GetUserId()))
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 508d7c70b137fa34f5dc31c00ec8fdf36de21a95..5346c72cd98f341e15b37b6d1ba75168ae5b0058 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2901,6 +2901,19 @@ _copyCreateEnumStmt(CreateEnumStmt *from)
 	return newnode;
 }
 
+static AlterEnumStmt *
+_copyAlterEnumStmt(AlterEnumStmt *from)
+{
+	AlterEnumStmt *newnode = makeNode(AlterEnumStmt);
+
+	COPY_NODE_FIELD(typeName);
+	COPY_STRING_FIELD(newVal);
+	COPY_STRING_FIELD(newValNeighbor);
+	COPY_SCALAR_FIELD(newValIsAfter);
+
+	return newnode;
+}
+
 static ViewStmt *
 _copyViewStmt(ViewStmt *from)
 {
@@ -4064,6 +4077,9 @@ copyObject(void *from)
 		case T_CreateEnumStmt:
 			retval = _copyCreateEnumStmt(from);
 			break;
+		case T_AlterEnumStmt:
+			retval = _copyAlterEnumStmt(from);
+			break;
 		case T_ViewStmt:
 			retval = _copyViewStmt(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 19262aad6691ea8caca2c7a20fa07a2de0b4d59f..7cb2192d94725734405377864c36fc3c0271e2b7 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1392,6 +1392,17 @@ _equalCreateEnumStmt(CreateEnumStmt *a, CreateEnumStmt *b)
 	return true;
 }
 
+static bool
+_equalAlterEnumStmt(AlterEnumStmt *a, AlterEnumStmt *b)
+{
+	COMPARE_NODE_FIELD(typeName);
+	COMPARE_STRING_FIELD(newVal);
+	COMPARE_STRING_FIELD(newValNeighbor);
+	COMPARE_SCALAR_FIELD(newValIsAfter);
+
+	return true;
+}
+
 static bool
 _equalViewStmt(ViewStmt *a, ViewStmt *b)
 {
@@ -2700,6 +2711,9 @@ equal(void *a, void *b)
 		case T_CreateEnumStmt:
 			retval = _equalCreateEnumStmt(a, b);
 			break;
+		case T_AlterEnumStmt:
+			retval = _equalAlterEnumStmt(a, b);
+			break;
 		case T_ViewStmt:
 			retval = _equalViewStmt(a, b);
 			break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c4165f0bf074f1be23e4bbdb4608fec2cbd76563..1394b21dec4de7400a466269ba6b36bb6390ce4e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -182,8 +182,8 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
 }
 
 %type <node>	stmt schema_stmt
-		AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterFdwStmt
-		AlterForeignServerStmt AlterGroupStmt
+		AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
+		AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
 		AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
 		AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
 		AlterRoleStmt AlterRoleSetStmt
@@ -652,6 +652,7 @@ stmt :
 			| AlterDatabaseSetStmt
 			| AlterDefaultPrivilegesStmt
 			| AlterDomainStmt
+			| AlterEnumStmt
 			| AlterFdwStmt
 			| AlterForeignServerStmt
 			| AlterFunctionStmt
@@ -3863,6 +3864,42 @@ enum_val_list:	Sconst
 				{ $$ = lappend($1, makeString($3)); }
 		;
 
+/*****************************************************************************
+ *
+ *	ALTER TYPE enumtype ADD ...
+ *
+ *****************************************************************************/
+
+AlterEnumStmt:
+         ALTER TYPE_P any_name ADD_P Sconst
+			 {
+				 AlterEnumStmt *n = makeNode(AlterEnumStmt);
+				 n->typeName = $3;
+				 n->newVal = $5;
+				 n->newValNeighbor = NULL;
+				 n->newValIsAfter = true;
+				 $$ = (Node *) n;
+			 }
+		 | ALTER TYPE_P any_name ADD_P Sconst BEFORE Sconst
+			 {
+				 AlterEnumStmt *n = makeNode(AlterEnumStmt);
+				 n->typeName = $3;
+				 n->newVal = $5;
+				 n->newValNeighbor = $7;
+				 n->newValIsAfter = false;
+				 $$ = (Node *) n;
+			 }
+		 | ALTER TYPE_P any_name ADD_P Sconst AFTER Sconst
+			 {
+				 AlterEnumStmt *n = makeNode(AlterEnumStmt);
+				 n->typeName = $3;
+				 n->newVal = $5;
+				 n->newValNeighbor = $7;
+				 n->newValIsAfter = true;
+				 $$ = (Node *) n;
+			 }
+		 ;
+
 
 /*****************************************************************************
  *
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 75cb354ea89db9ce071ab3d781c8daf3280cd5e4..2300e882499d52563aea46b57c85cd650ebdf37c 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -190,6 +190,7 @@ check_xact_readonly(Node *parsetree)
 		case T_CreateTrigStmt:
 		case T_CompositeTypeStmt:
 		case T_CreateEnumStmt:
+		case T_AlterEnumStmt:
 		case T_ViewStmt:
 		case T_DropCastStmt:
 		case T_DropStmt:
@@ -860,6 +861,16 @@ standard_ProcessUtility(Node *parsetree,
 			DefineEnum((CreateEnumStmt *) parsetree);
 			break;
 
+		case T_AlterEnumStmt:	/* ALTER TYPE (enum) */
+			/*
+			 * We disallow this in transaction blocks, because we can't cope
+			 * with enum OID values getting into indexes and then having their
+			 * defining pg_enum entries go away.
+			 */
+			PreventTransactionChain(isTopLevel, "ALTER TYPE ... ADD");
+			AlterEnum((AlterEnumStmt *) parsetree);
+			break;
+
 		case T_ViewStmt:		/* CREATE VIEW */
 			DefineView((ViewStmt *) parsetree, queryString);
 			break;
@@ -1868,6 +1879,10 @@ CreateCommandTag(Node *parsetree)
 			tag = "CREATE TYPE";
 			break;
 
+		case T_AlterEnumStmt:
+			tag = "ALTER TYPE";
+			break;
+
 		case T_ViewStmt:
 			tag = "CREATE VIEW";
 			break;
@@ -2410,6 +2425,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_AlterEnumStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
 		case T_ViewStmt:
 			lev = LOGSTMT_DDL;
 			break;
diff --git a/src/backend/utils/adt/enum.c b/src/backend/utils/adt/enum.c
index e5747a46bcd341488b26cc739f50dc0f8d14f622..dd168dd97305d89e7c9f0cc7bf6a62b8c4c07661 100644
--- a/src/backend/utils/adt/enum.c
+++ b/src/backend/utils/adt/enum.c
@@ -13,18 +13,22 @@
  */
 #include "postgres.h"
 
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "catalog/indexing.h"
 #include "catalog/pg_enum.h"
 #include "fmgr.h"
+#include "libpq/pqformat.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
-#include "utils/lsyscache.h"
+#include "utils/fmgroids.h"
+#include "utils/snapmgr.h"
 #include "utils/syscache.h"
-#include "libpq/pqformat.h"
-#include "miscadmin.h"
+#include "utils/typcache.h"
 
 
+static Oid	enum_endpoint(Oid enumtypoid, ScanDirection direction);
 static ArrayType *enum_range_internal(Oid enumtypoid, Oid lower, Oid upper);
-static int	enum_elem_cmp(const void *left, const void *right);
 
 
 /* Basic I/O support */
@@ -155,13 +159,63 @@ enum_send(PG_FUNCTION_ARGS)
 
 /* Comparison functions and related */
 
+/*
+ * enum_cmp_internal is the common engine for all the visible comparison
+ * functions, except for enum_eq and enum_ne which can just check for OID
+ * equality directly.
+ */
+static int
+enum_cmp_internal(Oid arg1, Oid arg2, FunctionCallInfo fcinfo)
+{
+	TypeCacheEntry *tcache;
+
+	/* Equal OIDs are equal no matter what */
+	if (arg1 == arg2)
+		return 0;
+
+	/* Fast path: even-numbered Oids are known to compare correctly */
+	if ((arg1 & 1) == 0 && (arg2 & 1) == 0)
+	{
+		if (arg1 < arg2)
+			return -1;
+		else
+			return 1;
+	}
+
+	/* Locate the typcache entry for the enum type */
+	tcache = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
+	if (tcache == NULL)
+	{
+		HeapTuple	enum_tup;
+		Form_pg_enum en;
+		Oid			typeoid;
+
+		/* Get the OID of the enum type containing arg1 */
+		enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg1));
+		if (!HeapTupleIsValid(enum_tup))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
+					 errmsg("invalid internal value for enum: %u",
+							arg1)));
+		en = (Form_pg_enum) GETSTRUCT(enum_tup);
+		typeoid = en->enumtypid;
+		ReleaseSysCache(enum_tup);
+		/* Now locate and remember the typcache entry */
+		tcache = lookup_type_cache(typeoid, 0);
+		fcinfo->flinfo->fn_extra = (void *) tcache;
+	}
+
+	/* The remaining comparison logic is in typcache.c */
+	return compare_values_of_enum(tcache, arg1, arg2);
+}
+
 Datum
 enum_lt(PG_FUNCTION_ARGS)
 {
 	Oid			a = PG_GETARG_OID(0);
 	Oid			b = PG_GETARG_OID(1);
 
-	PG_RETURN_BOOL(a < b);
+	PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) < 0);
 }
 
 Datum
@@ -170,7 +224,7 @@ enum_le(PG_FUNCTION_ARGS)
 	Oid			a = PG_GETARG_OID(0);
 	Oid			b = PG_GETARG_OID(1);
 
-	PG_RETURN_BOOL(a <= b);
+	PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) <= 0);
 }
 
 Datum
@@ -197,7 +251,7 @@ enum_ge(PG_FUNCTION_ARGS)
 	Oid			a = PG_GETARG_OID(0);
 	Oid			b = PG_GETARG_OID(1);
 
-	PG_RETURN_BOOL(a >= b);
+	PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) >= 0);
 }
 
 Datum
@@ -206,7 +260,7 @@ enum_gt(PG_FUNCTION_ARGS)
 	Oid			a = PG_GETARG_OID(0);
 	Oid			b = PG_GETARG_OID(1);
 
-	PG_RETURN_BOOL(a > b);
+	PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) > 0);
 }
 
 Datum
@@ -215,7 +269,7 @@ enum_smaller(PG_FUNCTION_ARGS)
 	Oid			a = PG_GETARG_OID(0);
 	Oid			b = PG_GETARG_OID(1);
 
-	PG_RETURN_OID(a <= b ? a : b);
+	PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) < 0 ? a : b);
 }
 
 Datum
@@ -224,7 +278,7 @@ enum_larger(PG_FUNCTION_ARGS)
 	Oid			a = PG_GETARG_OID(0);
 	Oid			b = PG_GETARG_OID(1);
 
-	PG_RETURN_OID(a >= b ? a : b);
+	PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) > 0 ? a : b);
 }
 
 Datum
@@ -233,24 +287,63 @@ enum_cmp(PG_FUNCTION_ARGS)
 	Oid			a = PG_GETARG_OID(0);
 	Oid			b = PG_GETARG_OID(1);
 
-	if (a > b)
-		PG_RETURN_INT32(1);
-	else if (a == b)
+	if (a == b)
 		PG_RETURN_INT32(0);
+	else if (enum_cmp_internal(a, b, fcinfo) > 0)
+		PG_RETURN_INT32(1);
 	else
 		PG_RETURN_INT32(-1);
 }
 
 /* Enum programming support functions */
 
+/*
+ * enum_endpoint: common code for enum_first/enum_last
+ */
+static Oid
+enum_endpoint(Oid enumtypoid, ScanDirection direction)
+{
+	Relation	enum_rel;
+	Relation	enum_idx;
+	SysScanDesc enum_scan;
+	HeapTuple	enum_tuple;
+	ScanKeyData skey;
+	Oid			minmax;
+
+	/*
+	 * Find the first/last enum member using pg_enum_typid_sortorder_index.
+	 * Note we must not use the syscache, and must use an MVCC snapshot here.
+	 * See comments for RenumberEnumType in catalog/pg_enum.c for more info.
+	 */
+	ScanKeyInit(&skey,
+				Anum_pg_enum_enumtypid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(enumtypoid));
+
+	enum_rel = heap_open(EnumRelationId, AccessShareLock);
+	enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock);
+	enum_scan = systable_beginscan_ordered(enum_rel, enum_idx,
+										   GetTransactionSnapshot(),
+										   1, &skey);
+
+	enum_tuple = systable_getnext_ordered(enum_scan, direction);
+	if (HeapTupleIsValid(enum_tuple))
+		minmax = HeapTupleGetOid(enum_tuple);
+	else
+		minmax = InvalidOid;
+
+	systable_endscan_ordered(enum_scan);
+	index_close(enum_idx, AccessShareLock);
+	heap_close(enum_rel, AccessShareLock);
+
+	return minmax;
+}
+
 Datum
 enum_first(PG_FUNCTION_ARGS)
 {
 	Oid			enumtypoid;
-	Oid			min = InvalidOid;
-	CatCList   *list;
-	int			num,
-				i;
+	Oid			min;
 
 	/*
 	 * We rely on being able to get the specific enum type from the calling
@@ -263,21 +356,14 @@ enum_first(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("could not determine actual enum type")));
 
-	list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid));
-	num = list->n_members;
-	for (i = 0; i < num; i++)
-	{
-		Oid			valoid = HeapTupleHeaderGetOid(list->members[i]->tuple.t_data);
-
-		if (!OidIsValid(min) || valoid < min)
-			min = valoid;
-	}
-
-	ReleaseCatCacheList(list);
+	/* Get the OID using the index */
+	min = enum_endpoint(enumtypoid, ForwardScanDirection);
 
-	if (!OidIsValid(min))		/* should not happen */
-		elog(ERROR, "no values found for enum %s",
-			 format_type_be(enumtypoid));
+	if (!OidIsValid(min))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("enum %s contains no values",
+						format_type_be(enumtypoid))));
 
 	PG_RETURN_OID(min);
 }
@@ -286,10 +372,7 @@ Datum
 enum_last(PG_FUNCTION_ARGS)
 {
 	Oid			enumtypoid;
-	Oid			max = InvalidOid;
-	CatCList   *list;
-	int			num,
-				i;
+	Oid			max;
 
 	/*
 	 * We rely on being able to get the specific enum type from the calling
@@ -302,21 +385,14 @@ enum_last(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("could not determine actual enum type")));
 
-	list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid));
-	num = list->n_members;
-	for (i = 0; i < num; i++)
-	{
-		Oid			valoid = HeapTupleHeaderGetOid(list->members[i]->tuple.t_data);
-
-		if (!OidIsValid(max) || valoid > max)
-			max = valoid;
-	}
-
-	ReleaseCatCacheList(list);
+	/* Get the OID using the index */
+	max = enum_endpoint(enumtypoid, BackwardScanDirection);
 
-	if (!OidIsValid(max))		/* should not happen */
-		elog(ERROR, "no values found for enum %s",
-			 format_type_be(enumtypoid));
+	if (!OidIsValid(max))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("enum %s contains no values",
+						format_type_be(enumtypoid))));
 
 	PG_RETURN_OID(max);
 }
@@ -377,51 +453,68 @@ static ArrayType *
 enum_range_internal(Oid enumtypoid, Oid lower, Oid upper)
 {
 	ArrayType  *result;
-	CatCList   *list;
-	int			total,
-				i,
-				j;
+	Relation	enum_rel;
+	Relation	enum_idx;
+	SysScanDesc enum_scan;
+	HeapTuple	enum_tuple;
+	ScanKeyData skey;
 	Datum	   *elems;
+	int			max,
+				cnt;
+	bool        left_found;
 
-	list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid));
-	total = list->n_members;
+	/*
+	 * Scan the enum members in order using pg_enum_typid_sortorder_index.
+	 * Note we must not use the syscache, and must use an MVCC snapshot here.
+	 * See comments for RenumberEnumType in catalog/pg_enum.c for more info.
+	 */
+	ScanKeyInit(&skey,
+				Anum_pg_enum_enumtypid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(enumtypoid));
+
+	enum_rel = heap_open(EnumRelationId, AccessShareLock);
+	enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock);
+	enum_scan = systable_beginscan_ordered(enum_rel, enum_idx,
+										   GetTransactionSnapshot(),
+										   1, &skey);
+
+	max = 64;
+	elems = (Datum *) palloc(max * sizeof(Datum));
+	cnt = 0;
+	left_found = !OidIsValid(lower);
+
+	while (HeapTupleIsValid(enum_tuple = systable_getnext_ordered(enum_scan, ForwardScanDirection)))
+	{
+		Oid		enum_oid = HeapTupleGetOid(enum_tuple);
 
-	elems = (Datum *) palloc(total * sizeof(Datum));
+		if (!left_found && lower == enum_oid)
+			left_found = true;
 
-	j = 0;
-	for (i = 0; i < total; i++)
-	{
-		Oid			val = HeapTupleGetOid(&(list->members[i]->tuple));
+		if (left_found)
+		{
+			if (cnt >= max)
+			{
+				max *= 2;
+				elems = (Datum *) repalloc(elems, max * sizeof(Datum));
+			}
 
-		if ((!OidIsValid(lower) || lower <= val) &&
-			(!OidIsValid(upper) || val <= upper))
-			elems[j++] = ObjectIdGetDatum(val);
-	}
+			elems[cnt++] = ObjectIdGetDatum(enum_oid);
+		}
 
-	/* shouldn't need the cache anymore */
-	ReleaseCatCacheList(list);
+		if (OidIsValid(upper) && upper == enum_oid)
+			break;
+	}
 
-	/* sort results into OID order */
-	qsort(elems, j, sizeof(Datum), enum_elem_cmp);
+	systable_endscan_ordered(enum_scan);
+	index_close(enum_idx, AccessShareLock);
+	heap_close(enum_rel, AccessShareLock);
 
+	/* and build the result array */
 	/* note this hardwires some details about the representation of Oid */
-	result = construct_array(elems, j, enumtypoid, sizeof(Oid), true, 'i');
+	result = construct_array(elems, cnt, enumtypoid, sizeof(Oid), true, 'i');
 
 	pfree(elems);
 
 	return result;
 }
-
-/* qsort comparison function for Datums that are OIDs */
-static int
-enum_elem_cmp(const void *left, const void *right)
-{
-	Oid			l = DatumGetObjectId(*((const Datum *) left));
-	Oid			r = DatumGetObjectId(*((const Datum *) right));
-
-	if (l < r)
-		return -1;
-	if (l > r)
-		return 1;
-	return 0;
-}
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 200961448903b12cabb11b895629404ca0b3d90a..bc4abc451a60523811778b7045274e92f37bbd96 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -42,22 +42,44 @@
  */
 #include "postgres.h"
 
+#include <limits.h>
+
 #include "access/hash.h"
 #include "access/heapam.h"
 #include "access/nbtree.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_enum.h"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
 #include "utils/builtins.h"
+#include "utils/fmgroids.h"
 #include "utils/inval.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"
 
 
 /* The main type cache hashtable searched by lookup_type_cache */
 static HTAB *TypeCacheHash = NULL;
 
+/* Private information to support comparisons of enum values */
+typedef struct
+{
+	Oid			enum_oid;		/* OID of one enum value */
+	float4		sort_order;		/* its sort position */
+} EnumItem;
+
+typedef struct TypeCacheEnumData
+{
+	Oid			bitmap_base;	/* OID corresponding to bit 0 of bitmapset */
+	Bitmapset  *sorted_values;	/* Set of OIDs known to be in order */
+	int			num_values;		/* total number of values in enum */
+	EnumItem	enum_values[1];	/* VARIABLE LENGTH ARRAY */
+} TypeCacheEnumData;
+
 /*
  * We use a separate table for storing the definitions of non-anonymous
  * record types.  Once defined, a record type will be remembered for the
@@ -88,6 +110,9 @@ static int32 RecordCacheArrayLen = 0;	/* allocated length of array */
 static int32 NextRecordTypmod = 0;		/* number of entries used */
 
 static void TypeCacheRelCallback(Datum arg, Oid relid);
+static void load_enum_cache_data(TypeCacheEntry *tcache);
+static EnumItem *find_enumitem(TypeCacheEnumData *enumdata, Oid arg);
+static int	enum_oid_cmp(const void *left, const void *right);
 
 
 /*
@@ -551,3 +576,300 @@ TypeCacheRelCallback(Datum arg, Oid relid)
 		}
 	}
 }
+
+
+/*
+ * Check if given OID is part of the subset that's sortable by comparisons
+ */
+static inline bool
+enum_known_sorted(TypeCacheEnumData *enumdata, Oid arg)
+{
+	Oid			offset;
+
+	if (arg < enumdata->bitmap_base)
+		return false;
+	offset = arg - enumdata->bitmap_base;
+	if (offset > (Oid) INT_MAX)
+		return false;
+	return bms_is_member((int) offset, enumdata->sorted_values);
+}
+
+
+/*
+ * compare_values_of_enum
+ *		Compare two members of an enum type.
+ *		Return <0, 0, or >0 according as arg1 <, =, or > arg2.
+ *
+ * Note: currently, the enumData cache is refreshed only if we are asked
+ * to compare an enum value that is not already in the cache.  This is okay
+ * because there is no support for re-ordering existing values, so comparisons
+ * of previously cached values will return the right answer even if other
+ * values have been added since we last loaded the cache.
+ *
+ * Note: the enum logic has a special-case rule about even-numbered versus
+ * odd-numbered OIDs, but we take no account of that rule here; this
+ * routine shouldn't even get called when that rule applies.
+ */
+int
+compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2)
+{
+	TypeCacheEnumData *enumdata;
+	EnumItem   *item1;
+	EnumItem   *item2;
+
+	/*
+	 * Equal OIDs are certainly equal --- this case was probably handled
+	 * by our caller, but we may as well check.
+	 */
+	if (arg1 == arg2)
+		return 0;
+
+	/* Load up the cache if first time through */
+	if (tcache->enumData == NULL)
+		load_enum_cache_data(tcache);
+	enumdata = tcache->enumData;
+
+	/*
+	 * If both OIDs are known-sorted, we can just compare them directly.
+	 */
+	if (enum_known_sorted(enumdata, arg1) &&
+		enum_known_sorted(enumdata, arg2))
+	{
+		if (arg1 < arg2)
+			return -1;
+		else
+			return 1;
+	}
+
+	/*
+	 * Slow path: we have to identify their actual sort-order positions.
+	 */
+	item1 = find_enumitem(enumdata, arg1);
+	item2 = find_enumitem(enumdata, arg2);
+
+	if (item1 == NULL || item2 == NULL)
+	{
+		/*
+		 * We couldn't find one or both values.  That means the enum has
+		 * changed under us, so re-initialize the cache and try again.
+		 * We don't bother retrying the known-sorted case in this path.
+		 */
+		load_enum_cache_data(tcache);
+		enumdata = tcache->enumData;
+
+		item1 = find_enumitem(enumdata, arg1);
+		item2 = find_enumitem(enumdata, arg2);
+
+		/*
+		 * If we still can't find the values, complain: we must have
+		 * corrupt data.
+		 */
+		if (item1 == NULL)
+			elog(ERROR, "enum value %u not found in cache for enum %s",
+				 arg1, format_type_be(tcache->type_id));
+		if (item2 == NULL)
+			elog(ERROR, "enum value %u not found in cache for enum %s",
+				 arg2, format_type_be(tcache->type_id));
+	}
+
+	if (item1->sort_order < item2->sort_order)
+		return -1;
+	else if (item1->sort_order > item2->sort_order)
+		return 1;
+	else
+		return 0;
+}
+
+/*
+ * Load (or re-load) the enumData member of the typcache entry.
+ */
+static void
+load_enum_cache_data(TypeCacheEntry *tcache)
+{
+	TypeCacheEnumData *enumdata;
+	Relation	enum_rel;
+	SysScanDesc enum_scan;
+	HeapTuple	enum_tuple;
+	ScanKeyData skey;
+	EnumItem   *items;
+	int			numitems;
+	int			maxitems;
+	Oid			bitmap_base;
+	Bitmapset  *bitmap;
+	MemoryContext oldcxt;
+	int			bm_size,
+				start_pos;
+
+	/* Check that this is actually an enum */
+	if (tcache->typtype != TYPTYPE_ENUM)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("%s is not an enum",
+						format_type_be(tcache->type_id))));
+
+	/*
+	 * Read all the information for members of the enum type.  We collect
+	 * the info in working memory in the caller's context, and then transfer
+	 * it to permanent memory in CacheMemoryContext.  This minimizes the risk
+	 * of leaking memory from CacheMemoryContext in the event of an error
+	 * partway through.
+	 */
+	maxitems = 64;
+	items = (EnumItem *) palloc(sizeof(EnumItem) * maxitems);
+	numitems = 0;
+
+	/*
+	 * Scan pg_enum for the members of the target enum type.  We use a
+	 * current MVCC snapshot, *not* SnapshotNow, so that we see a consistent
+	 * set of rows even if someone commits a renumbering of the enum meanwhile.
+	 * See comments for RenumberEnumType in catalog/pg_enum.c for more info.
+	 */
+	ScanKeyInit(&skey,
+				Anum_pg_enum_enumtypid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(tcache->type_id));
+
+	enum_rel = heap_open(EnumRelationId, AccessShareLock);
+	enum_scan = systable_beginscan(enum_rel,
+								   EnumTypIdLabelIndexId,
+								   true, GetTransactionSnapshot(),
+								   1, &skey);
+
+	while (HeapTupleIsValid(enum_tuple = systable_getnext(enum_scan)))
+	{
+		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(enum_tuple);
+
+		if (numitems >= maxitems)
+		{
+			maxitems *= 2;
+			items = (EnumItem *) repalloc(items, sizeof(EnumItem) * maxitems);
+		}
+		items[numitems].enum_oid = HeapTupleGetOid(enum_tuple);
+		items[numitems].sort_order = en->enumsortorder;
+		numitems++;
+	}
+
+	systable_endscan(enum_scan);
+	heap_close(enum_rel, AccessShareLock);
+
+	/* Sort the items into OID order */
+	qsort(items, numitems, sizeof(EnumItem), enum_oid_cmp);
+
+	/*
+	 * Here, we create a bitmap listing a subset of the enum's OIDs that are
+	 * known to be in order and can thus be compared with just OID comparison.
+	 *
+	 * The point of this is that the enum's initial OIDs were certainly in
+	 * order, so there is some subset that can be compared via OID comparison;
+	 * and we'd rather not do binary searches unnecessarily.
+	 *
+	 * This is somewhat heuristic, and might identify a subset of OIDs that
+	 * isn't exactly what the type started with.  That's okay as long as
+	 * the subset is correctly sorted.
+	 */
+	bitmap_base = InvalidOid;
+	bitmap = NULL;
+	bm_size = 1;				/* only save sets of at least 2 OIDs */
+
+	for (start_pos = 0; start_pos < numitems - 1; start_pos++)
+	{
+		/*
+		 * Identify longest sorted subsequence starting at start_pos
+		 */
+		Bitmapset *this_bitmap = bms_make_singleton(0);
+		int		this_bm_size = 1;
+		Oid		start_oid = items[start_pos].enum_oid;
+		float4	prev_order = items[start_pos].sort_order;
+		int		i;
+
+		for (i = start_pos + 1; i < numitems; i++)
+		{
+			Oid		offset;
+
+			offset = items[i].enum_oid - start_oid;
+			/* quit if bitmap would be too large; cutoff is arbitrary */
+			if (offset >= 8192)
+				break;
+			/* include the item if it's in-order */
+			if (items[i].sort_order > prev_order)
+			{
+				prev_order = items[i].sort_order;
+				this_bitmap = bms_add_member(this_bitmap, (int) offset);
+				this_bm_size++;
+			}
+		}
+
+		/* Remember it if larger than previous best */
+		if (this_bm_size > bm_size)
+		{
+			bms_free(bitmap);
+			bitmap_base = start_oid;
+			bitmap = this_bitmap;
+			bm_size = this_bm_size;
+		}
+		else
+			bms_free(this_bitmap);
+
+		/*
+		 * Done if it's not possible to find a longer sequence in the rest
+		 * of the list.  In typical cases this will happen on the first
+		 * iteration, which is why we create the bitmaps on the fly instead
+		 * of doing a second pass over the list.
+		 */
+		if (bm_size >= (numitems - start_pos - 1))
+			break;
+	}
+
+	/* OK, copy the data into CacheMemoryContext */
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	enumdata = (TypeCacheEnumData *)
+		palloc(offsetof(TypeCacheEnumData, enum_values) +
+			   numitems * sizeof(EnumItem));
+	enumdata->bitmap_base = bitmap_base;
+	enumdata->sorted_values = bms_copy(bitmap);
+	enumdata->num_values = numitems;
+	memcpy(enumdata->enum_values, items, numitems * sizeof(EnumItem));
+	MemoryContextSwitchTo(oldcxt);
+
+	pfree(items);
+	bms_free(bitmap);
+
+	/* And link the finished cache struct into the typcache */
+	if (tcache->enumData != NULL)
+		pfree(tcache->enumData);
+	tcache->enumData = enumdata;
+}
+
+/*
+ * Locate the EnumItem with the given OID, if present
+ */
+static EnumItem *
+find_enumitem(TypeCacheEnumData *enumdata, Oid arg)
+{
+	EnumItem	srch;
+
+	/* On some versions of Solaris, bsearch of zero items dumps core */
+	if (enumdata->num_values <= 0)
+		return NULL;
+
+	srch.enum_oid = arg;
+	return bsearch(&srch, enumdata->enum_values, enumdata->num_values,
+				   sizeof(EnumItem), enum_oid_cmp);
+}
+
+/*
+ * qsort comparison function for OID-ordered EnumItems
+ */
+static int
+enum_oid_cmp(const void *left, const void *right)
+{
+	const EnumItem *l = (const EnumItem *) left;
+	const EnumItem *r = (const EnumItem *) right;
+
+	if (l->enum_oid < r->enum_oid)
+		return -1;
+	else if (l->enum_oid > r->enum_oid)
+		return 1;
+	else
+		return 0;
+}
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 6a4557b48610f51247209b80ea5b8962f1fea048..55ea6841a443ca0558e526a895cfbf2f484d76ba 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6657,14 +6657,21 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 	Oid			enum_oid;
 	char	   *label;
 
-	/* Set proper schema search path so regproc references list correctly */
-	selectSourceSchema(tyinfo->dobj.namespace->dobj.name);
+	/* Set proper schema search path */
+	selectSourceSchema("pg_catalog");
 
-	appendPQExpBuffer(query, "SELECT oid, enumlabel "
-					  "FROM pg_catalog.pg_enum "
-					  "WHERE enumtypid = '%u'"
-					  "ORDER BY oid",
-					  tyinfo->dobj.catId.oid);
+	if (fout->remoteVersion >= 90100)
+		appendPQExpBuffer(query, "SELECT oid, enumlabel "
+						  "FROM pg_catalog.pg_enum "
+						  "WHERE enumtypid = '%u'"
+						  "ORDER BY enumsortorder",
+						  tyinfo->dobj.catId.oid);
+	else
+		appendPQExpBuffer(query, "SELECT oid, enumlabel "
+						  "FROM pg_catalog.pg_enum "
+						  "WHERE enumtypid = '%u'"
+						  "ORDER BY oid",
+						  tyinfo->dobj.catId.oid);
 
 	res = PQexec(g_conn, query->data);
 	check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK);
@@ -6713,13 +6720,15 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 			if (i == 0)
 				appendPQExpBuffer(q, "\n-- For binary upgrade, must preserve pg_enum oids\n");
 			appendPQExpBuffer(q,
-			 "SELECT binary_upgrade.add_pg_enum_label('%u'::pg_catalog.oid, "
-							  "'%u'::pg_catalog.oid, ",
-							  enum_oid, tyinfo->dobj.catId.oid);
+							  "SELECT binary_upgrade.set_next_pg_enum_oid('%u'::pg_catalog.oid);\n",
+							  enum_oid);
+			appendPQExpBuffer(q, "ALTER TYPE %s.",
+							  fmtId(tyinfo->dobj.namespace->dobj.name));
+			appendPQExpBuffer(q, "%s ADD ",
+							  fmtId(tyinfo->dobj.name));
 			appendStringLiteralAH(q, label, fout);
-			appendPQExpBuffer(q, ");\n");
+			appendPQExpBuffer(q, ";\n\n");
 		}
-		appendPQExpBuffer(q, "\n");
 	}
 
 	ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId,
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 57d74e14d75e3556389cff966ab7fc360b97fdbd..b705cb29dd419b207a365ae61801f8a6512da253 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -473,17 +473,27 @@ describeTypes(const char *pattern, bool verbose, bool showSystem)
 						  gettext_noop("Internal name"),
 						  gettext_noop("Size"));
 	if (verbose && pset.sversion >= 80300)
+	{
 		appendPQExpBuffer(&buf,
 						  "  pg_catalog.array_to_string(\n"
 						  "      ARRAY(\n"
 						  "		     SELECT e.enumlabel\n"
 						  "          FROM pg_catalog.pg_enum e\n"
-						  "          WHERE e.enumtypid = t.oid\n"
-						  "          ORDER BY e.oid\n"
+						  "          WHERE e.enumtypid = t.oid\n");
+
+		if (pset.sversion >= 90100)
+			appendPQExpBuffer(&buf,
+							  "          ORDER BY e.enumsortorder\n");
+		else
+			appendPQExpBuffer(&buf,
+							  "          ORDER BY e.oid\n");
+
+		appendPQExpBuffer(&buf,
 						  "      ),\n"
 						  "      E'\\n'\n"
 						  "  ) AS \"%s\",\n",
 						  gettext_noop("Elements"));
+	}
 
 	appendPQExpBuffer(&buf,
 				"  pg_catalog.obj_description(t.oid, 'pg_type') as \"%s\"\n",
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index e30a7d7298b489286c2e8cccea45a32de8ceb556..b858d3ee009626cea6258d5da775b307e0e0b995 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	201010201
+#define CATALOG_VERSION_NO	201010241
 
 #endif
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index b7c9849314eb1dc2f468ea0a3c22fe2e63de2b67..a3839e1e2597e3d5ccfd848c69265eb9057080e7 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -147,6 +147,8 @@ DECLARE_UNIQUE_INDEX(pg_enum_oid_index, 3502, on pg_enum using btree(oid oid_ops
 #define EnumOidIndexId	3502
 DECLARE_UNIQUE_INDEX(pg_enum_typid_label_index, 3503, on pg_enum using btree(enumtypid oid_ops, enumlabel name_ops));
 #define EnumTypIdLabelIndexId 3503
+DECLARE_UNIQUE_INDEX(pg_enum_typid_sortorder_index, 3534, on pg_enum using btree(enumtypid oid_ops, enumsortorder float4_ops));
+#define EnumTypIdSortOrderIndexId 3534
 
 /* This following index is not used for a cache and is not unique */
 DECLARE_INDEX(pg_index_indrelid_index, 2678, on pg_index using btree(indrelid oid_ops));
diff --git a/src/include/catalog/pg_enum.h b/src/include/catalog/pg_enum.h
index 28da42bf54e417047c08b0a94c82e1baad61e37e..cc69eb531c6e558c87b41794543598452b0ec390 100644
--- a/src/include/catalog/pg_enum.h
+++ b/src/include/catalog/pg_enum.h
@@ -34,6 +34,7 @@
 CATALOG(pg_enum,3501)
 {
 	Oid			enumtypid;		/* OID of owning enum type */
+	float4		enumsortorder;	/* sort position of this enum value */
 	NameData	enumlabel;		/* text representation of enum value */
 } FormData_pg_enum;
 
@@ -48,9 +49,10 @@ typedef FormData_pg_enum *Form_pg_enum;
  *		compiler constants for pg_enum
  * ----------------
  */
-#define Natts_pg_enum					2
+#define Natts_pg_enum					3
 #define Anum_pg_enum_enumtypid			1
-#define Anum_pg_enum_enumlabel			2
+#define Anum_pg_enum_enumsortorder		2
+#define Anum_pg_enum_enumlabel			3
 
 /* ----------------
  *		pg_enum has no initial contents
@@ -60,8 +62,9 @@ typedef FormData_pg_enum *Form_pg_enum;
 /*
  * prototypes for functions in pg_enum.c
  */
-extern void EnumValuesCreate(Oid enumTypeOid, List *vals,
-				 Oid binary_upgrade_next_pg_enum_oid);
+extern void EnumValuesCreate(Oid enumTypeOid, List *vals);
 extern void EnumValuesDelete(Oid enumTypeOid);
+extern void AddEnumLabel(Oid enumTypeOid, const char *newVal,
+						 const char *neighbor, bool newValIsAfter);
 
 #endif   /* PG_ENUM_H */
diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h
index 2bff7e1c2b138481d33aebfd2f03504815f7d02a..4c6e7e164e0a3da23333014e0f9bac9ca5c170df 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -24,6 +24,7 @@ extern void RemoveTypes(DropStmt *drop);
 extern void RemoveTypeById(Oid typeOid);
 extern void DefineDomain(CreateDomainStmt *stmt);
 extern void DefineEnum(CreateEnumStmt *stmt);
+extern void AlterEnum(AlterEnumStmt *stmt);
 extern Oid	DefineCompositeType(const RangeVar *typevar, List *coldeflist);
 extern Oid	AssignTypeArrayOid(void);
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 15dabe31ce39750f35e77ebcaf274ff40df313a0..8e94d9803f702875cd47b85b48b1ebe29ac257b9 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -338,6 +338,7 @@ typedef enum NodeTag
 	T_ReassignOwnedStmt,
 	T_CompositeTypeStmt,
 	T_CreateEnumStmt,
+	T_AlterEnumStmt,
 	T_AlterTSDictionaryStmt,
 	T_AlterTSConfigurationStmt,
 	T_CreateFdwStmt,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e0bdebd0abe378727fcfb858bae31c503a8df6d0..3cac54b94708e574fc7f53bba4317be8d00b4855 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2193,6 +2193,18 @@ typedef struct CreateEnumStmt
 	List	   *vals;			/* enum values (list of Value strings) */
 } CreateEnumStmt;
 
+/* ----------------------
+ *		Alter Type Statement, enum types
+ * ----------------------
+ */
+typedef struct AlterEnumStmt
+{
+	NodeTag		type;
+	List	   *typeName;		/* qualified name (list of Value strings) */
+	char	   *newVal;			/* new enum value's name */
+	char	   *newValNeighbor;	/* neighboring enum value, if specified */
+	bool	    newValIsAfter;	/* place new enum value after neighbor? */
+} AlterEnumStmt;
 
 /* ----------------------
  *		Create View Statement
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index 4065e483e4be6cdfbce1839df2f6ffe19a81aa0c..28b66718b338c9a75276a027b25dc78f7b9fee50 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -20,6 +20,9 @@
 #include "fmgr.h"
 
 
+/* TypeCacheEnumData is an opaque struct known only within typcache.c */
+struct TypeCacheEnumData;
+
 typedef struct TypeCacheEntry
 {
 	/* typeId is the hash lookup key and MUST BE FIRST */
@@ -63,6 +66,12 @@ typedef struct TypeCacheEntry
 	 * reference-counted tupledesc.)
 	 */
 	TupleDesc	tupDesc;
+
+	/*
+	 * Private information about an enum type.  NULL if not enum or
+	 * information hasn't been requested.
+	 */
+	struct TypeCacheEnumData *enumData;
 } TypeCacheEntry;
 
 /* Bit flags to indicate which fields a given caller needs to have set */
@@ -86,4 +95,6 @@ extern TupleDesc lookup_rowtype_tupdesc_copy(Oid type_id, int32 typmod);
 
 extern void assign_record_type_typmod(TupleDesc tupDesc);
 
+extern int	compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2);
+
 #endif   /* TYPCACHE_H */
diff --git a/src/test/regress/expected/enum.out b/src/test/regress/expected/enum.out
index 56240c0e7a2966a9dccd2cd9692bc628c52237ab..b1ba3f1fad3fca82e5f750366708453e253633fb 100644
--- a/src/test/regress/expected/enum.out
+++ b/src/test/regress/expected/enum.out
@@ -24,6 +24,155 @@ SELECT 'mauve'::rainbow;
 ERROR:  invalid input value for enum rainbow: "mauve"
 LINE 1: SELECT 'mauve'::rainbow;
                ^
+--
+-- adding new values
+--
+CREATE TYPE planets AS ENUM ( 'venus', 'earth', 'mars' );
+SELECT enumlabel, enumsortorder
+FROM pg_enum
+WHERE enumtypid = 'planets'::regtype
+ORDER BY 2;
+ enumlabel | enumsortorder 
+-----------+---------------
+ venus     |             1
+ earth     |             2
+ mars      |             3
+(3 rows)
+
+ALTER TYPE planets ADD 'uranus';
+SELECT enumlabel, enumsortorder
+FROM pg_enum
+WHERE enumtypid = 'planets'::regtype
+ORDER BY 2;
+ enumlabel | enumsortorder 
+-----------+---------------
+ venus     |             1
+ earth     |             2
+ mars      |             3
+ uranus    |             4
+(4 rows)
+
+ALTER TYPE planets ADD 'mercury' BEFORE 'venus';
+ALTER TYPE planets ADD 'saturn' BEFORE 'uranus';
+ALTER TYPE planets ADD 'jupiter' AFTER 'mars';
+ALTER TYPE planets ADD 'neptune' AFTER 'uranus';
+SELECT enumlabel, enumsortorder
+FROM pg_enum
+WHERE enumtypid = 'planets'::regtype
+ORDER BY 2;
+ enumlabel | enumsortorder 
+-----------+---------------
+ mercury   |             0
+ venus     |             1
+ earth     |             2
+ mars      |             3
+ jupiter   |          3.25
+ saturn    |           3.5
+ uranus    |             4
+ neptune   |             5
+(8 rows)
+
+SELECT enumlabel, enumsortorder
+FROM pg_enum
+WHERE enumtypid = 'planets'::regtype
+ORDER BY enumlabel::planets;
+ enumlabel | enumsortorder 
+-----------+---------------
+ mercury   |             0
+ venus     |             1
+ earth     |             2
+ mars      |             3
+ jupiter   |          3.25
+ saturn    |           3.5
+ uranus    |             4
+ neptune   |             5
+(8 rows)
+
+-- errors for adding labels
+ALTER TYPE planets ADD
+  'plutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutopluto';
+ERROR:  invalid enum label "plutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutopluto"
+DETAIL:  Labels must be 63 characters or less.
+ALTER TYPE planets ADD 'pluto' AFTER 'zeus';
+ERROR:  "zeus" is not an existing enum label
+--
+-- Test inserting so many values that we have to renumber
+--
+create type insenum as enum ('L1', 'L2');
+alter type insenum add 'i1' before 'L2';
+alter type insenum add 'i2' before 'L2';
+alter type insenum add 'i3' before 'L2';
+alter type insenum add 'i4' before 'L2';
+alter type insenum add 'i5' before 'L2';
+alter type insenum add 'i6' before 'L2';
+alter type insenum add 'i7' before 'L2';
+alter type insenum add 'i8' before 'L2';
+alter type insenum add 'i9' before 'L2';
+alter type insenum add 'i10' before 'L2';
+alter type insenum add 'i11' before 'L2';
+alter type insenum add 'i12' before 'L2';
+alter type insenum add 'i13' before 'L2';
+alter type insenum add 'i14' before 'L2';
+alter type insenum add 'i15' before 'L2';
+alter type insenum add 'i16' before 'L2';
+alter type insenum add 'i17' before 'L2';
+alter type insenum add 'i18' before 'L2';
+alter type insenum add 'i19' before 'L2';
+alter type insenum add 'i20' before 'L2';
+alter type insenum add 'i21' before 'L2';
+alter type insenum add 'i22' before 'L2';
+alter type insenum add 'i23' before 'L2';
+alter type insenum add 'i24' before 'L2';
+alter type insenum add 'i25' before 'L2';
+alter type insenum add 'i26' before 'L2';
+alter type insenum add 'i27' before 'L2';
+alter type insenum add 'i28' before 'L2';
+alter type insenum add 'i29' before 'L2';
+alter type insenum add 'i30' before 'L2';
+-- The exact values of enumsortorder will now depend on the local properties
+-- of float4, but in any reasonable implementation we should get at least
+-- 20 splits before having to renumber; so only hide values > 20.
+SELECT enumlabel,
+       case when enumsortorder > 20 then null else enumsortorder end as so
+FROM pg_enum
+WHERE enumtypid = 'insenum'::regtype
+ORDER BY enumsortorder;
+ enumlabel | so 
+-----------+----
+ L1        |  1
+ i1        |  2
+ i2        |  3
+ i3        |  4
+ i4        |  5
+ i5        |  6
+ i6        |  7
+ i7        |  8
+ i8        |  9
+ i9        | 10
+ i10       | 11
+ i11       | 12
+ i12       | 13
+ i13       | 14
+ i14       | 15
+ i15       | 16
+ i16       | 17
+ i17       | 18
+ i18       | 19
+ i19       | 20
+ i20       |   
+ i21       |   
+ i22       |   
+ i23       |   
+ i24       |   
+ i25       |   
+ i26       |   
+ i27       |   
+ i28       |   
+ i29       |   
+ i30       |   
+ L2        |   
+(32 rows)
+
 --
 -- Basic table creation, row selection
 --
@@ -403,7 +552,7 @@ SELECT COUNT(*) FROM pg_type WHERE typname = 'rainbow';
 
 SELECT * FROM pg_enum WHERE NOT EXISTS
   (SELECT 1 FROM pg_type WHERE pg_type.oid = enumtypid);
- enumtypid | enumlabel 
------------+-----------
+ enumtypid | enumsortorder | enumlabel 
+-----------+---------------+-----------
 (0 rows)
 
diff --git a/src/test/regress/sql/enum.sql b/src/test/regress/sql/enum.sql
index 387e8e72ed8f8e9af85bd7430f9fbfde369a0e3e..70bf8c5f1ba8e4e458c3c75c75b352504a0b9228 100644
--- a/src/test/regress/sql/enum.sql
+++ b/src/test/regress/sql/enum.sql
@@ -15,6 +15,92 @@ SELECT COUNT(*) FROM pg_enum WHERE enumtypid = 'rainbow'::regtype;
 SELECT 'red'::rainbow;
 SELECT 'mauve'::rainbow;
 
+--
+-- adding new values
+--
+
+CREATE TYPE planets AS ENUM ( 'venus', 'earth', 'mars' );
+
+SELECT enumlabel, enumsortorder
+FROM pg_enum
+WHERE enumtypid = 'planets'::regtype
+ORDER BY 2;
+
+ALTER TYPE planets ADD 'uranus';
+
+SELECT enumlabel, enumsortorder
+FROM pg_enum
+WHERE enumtypid = 'planets'::regtype
+ORDER BY 2;
+
+ALTER TYPE planets ADD 'mercury' BEFORE 'venus';
+ALTER TYPE planets ADD 'saturn' BEFORE 'uranus';
+ALTER TYPE planets ADD 'jupiter' AFTER 'mars';
+ALTER TYPE planets ADD 'neptune' AFTER 'uranus';
+
+SELECT enumlabel, enumsortorder
+FROM pg_enum
+WHERE enumtypid = 'planets'::regtype
+ORDER BY 2;
+
+SELECT enumlabel, enumsortorder
+FROM pg_enum
+WHERE enumtypid = 'planets'::regtype
+ORDER BY enumlabel::planets;
+
+-- errors for adding labels
+ALTER TYPE planets ADD
+  'plutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutopluto';
+
+ALTER TYPE planets ADD 'pluto' AFTER 'zeus';
+
+--
+-- Test inserting so many values that we have to renumber
+--
+
+create type insenum as enum ('L1', 'L2');
+
+alter type insenum add 'i1' before 'L2';
+alter type insenum add 'i2' before 'L2';
+alter type insenum add 'i3' before 'L2';
+alter type insenum add 'i4' before 'L2';
+alter type insenum add 'i5' before 'L2';
+alter type insenum add 'i6' before 'L2';
+alter type insenum add 'i7' before 'L2';
+alter type insenum add 'i8' before 'L2';
+alter type insenum add 'i9' before 'L2';
+alter type insenum add 'i10' before 'L2';
+alter type insenum add 'i11' before 'L2';
+alter type insenum add 'i12' before 'L2';
+alter type insenum add 'i13' before 'L2';
+alter type insenum add 'i14' before 'L2';
+alter type insenum add 'i15' before 'L2';
+alter type insenum add 'i16' before 'L2';
+alter type insenum add 'i17' before 'L2';
+alter type insenum add 'i18' before 'L2';
+alter type insenum add 'i19' before 'L2';
+alter type insenum add 'i20' before 'L2';
+alter type insenum add 'i21' before 'L2';
+alter type insenum add 'i22' before 'L2';
+alter type insenum add 'i23' before 'L2';
+alter type insenum add 'i24' before 'L2';
+alter type insenum add 'i25' before 'L2';
+alter type insenum add 'i26' before 'L2';
+alter type insenum add 'i27' before 'L2';
+alter type insenum add 'i28' before 'L2';
+alter type insenum add 'i29' before 'L2';
+alter type insenum add 'i30' before 'L2';
+
+-- The exact values of enumsortorder will now depend on the local properties
+-- of float4, but in any reasonable implementation we should get at least
+-- 20 splits before having to renumber; so only hide values > 20.
+
+SELECT enumlabel,
+       case when enumsortorder > 20 then null else enumsortorder end as so
+FROM pg_enum
+WHERE enumtypid = 'insenum'::regtype
+ORDER BY enumsortorder;
+
 --
 -- Basic table creation, row selection
 --