From 84c123be1de8a9955741e20c9f945571e40c545e Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sun, 24 Oct 2010 23:04:37 -0400
Subject: [PATCH] Allow new values to be added to an existing enum type.

After much expenditure of effort, we've got this to the point where the
performance penalty is pretty minimal in typical cases.

Andrew Dunstan, reviewed by Brendan Jurd, Dean Rasheed, and Tom Lane
---
 contrib/pg_upgrade/function.c                 |   2 +-
 .../pg_upgrade_support/pg_upgrade_support.c   |  19 +-
 doc/src/sgml/catalogs.sgml                    |  34 +-
 doc/src/sgml/ref/alter_type.sgml              |  67 ++-
 src/backend/catalog/pg_enum.c                 | 402 ++++++++++++++++--
 src/backend/commands/typecmds.c               |  73 +++-
 src/backend/nodes/copyfuncs.c                 |  16 +
 src/backend/nodes/equalfuncs.c                |  14 +
 src/backend/parser/gram.y                     |  41 +-
 src/backend/tcop/utility.c                    |  19 +
 src/backend/utils/adt/enum.c                  | 259 +++++++----
 src/backend/utils/cache/typcache.c            | 322 ++++++++++++++
 src/bin/pg_dump/pg_dump.c                     |  33 +-
 src/bin/psql/describe.c                       |  14 +-
 src/include/catalog/catversion.h              |   2 +-
 src/include/catalog/indexing.h                |   2 +
 src/include/catalog/pg_enum.h                 |  11 +-
 src/include/commands/typecmds.h               |   1 +
 src/include/nodes/nodes.h                     |   1 +
 src/include/nodes/parsenodes.h                |  12 +
 src/include/utils/typcache.h                  |  11 +
 src/test/regress/expected/enum.out            | 153 ++++++-
 src/test/regress/sql/enum.sql                 |  86 ++++
 23 files changed, 1423 insertions(+), 171 deletions(-)

diff --git a/contrib/pg_upgrade/function.c b/contrib/pg_upgrade/function.c
index 31255d637dc..c76aaeb090b 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 c956be187ac..3ec436fe140 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 b7b48e4fb93..9a8729b8b31 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 315922ea836..90de2e81ef8 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 d544c1f4773..0c384def7b6 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 46b156e09a3..220be9b443b 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 508d7c70b13..5346c72cd98 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 19262aad669..7cb2192d947 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 c4165f0bf07..1394b21dec4 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 75cb354ea89..2300e882499 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 e5747a46bcd..dd168dd9730 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 20096144890..bc4abc451a6 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 6a4557b4861..55ea6841a44 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 57d74e14d75..b705cb29dd4 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 e30a7d7298b..b858d3ee009 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 b7c9849314e..a3839e1e259 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 28da42bf54e..cc69eb531c6 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 2bff7e1c2b1..4c6e7e164e0 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 15dabe31ce3..8e94d9803f7 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 e0bdebd0abe..3cac54b9470 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 4065e483e4b..28b66718b33 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 56240c0e7a2..b1ba3f1fad3 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 387e8e72ed8..70bf8c5f1ba 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
 --
-- 
GitLab