WIP: extensible enums

Started by Andrew Dunstanover 15 years ago125 messages
#1Andrew Dunstan
andrew@dunslane.net
1 attachment(s)

Attached is a WIP patch that allows enums to be extended with additional
labels arbitrarily. As previously discussed, it works by adding an
explicit sort order column to pg_enum. It keeps track of whether the
labels are correctly sorted by oid value, and if so uses that for
comparison, so the possible performance impact on existing uses, and on
almost all cases where a label is added at the end of the list, should
be negligible.

Open items include

* some additional error checking required
* missing documentation
* pg_upgrade considerations

To add a label at the end of the list, do:

ALTER TYPE myenum ADD 'newlabel';

To add a label somewhere else, do:

ALTER TYPE myenum ADD 'newlabel' BEFORE 'existinglabel';

or

ALTER TYPE myenum ADD 'newlabel' AFTER 'existinglabel';

I'm not wedded to the syntax. Let the bikeshedding begin.

cheers

andrew

Attachments:

venum.patchtext/x-patch; name=venum.patchDownload
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 854,860 **** AddNewRelationType(const char *typeName,
  				   'x',			/* fully TOASTable */
  				   -1,			/* typmod */
  				   0,			/* array dimensions for typBaseType */
! 				   false);		/* Type NOT NULL */
  }
  
  /* --------------------------------
--- 854,862 ----
  				   'x',			/* fully TOASTable */
  				   -1,			/* typmod */
  				   0,			/* array dimensions for typBaseType */
! 				   false,		/* Type NOT NULL */
! 				   -1,          /* no enum labels */
! 				   false);      /* type is not an enum, so not sorted */
  }
  
  /* --------------------------------
***************
*** 1086,1092 **** heap_create_with_catalog(const char *relname,
  				   'x',			/* fully TOASTable */
  				   -1,			/* typmod */
  				   0,			/* array dimensions for typBaseType */
! 				   false);		/* Type NOT NULL */
  
  		pfree(relarrayname);
  	}
--- 1088,1096 ----
  				   'x',			/* fully TOASTable */
  				   -1,			/* typmod */
  				   0,			/* array dimensions for typBaseType */
! 				   false,		/* Type NOT NULL */
! 				   -1,          /* no enum labels */
! 				   false);      /* type is not an enum, so not sorted */
  
  		pfree(relarrayname);
  	}
*** a/src/backend/catalog/pg_enum.c
--- b/src/backend/catalog/pg_enum.c
***************
*** 18,29 ****
--- 18,202 ----
  #include "catalog/catalog.h"
  #include "catalog/indexing.h"
  #include "catalog/pg_enum.h"
+ #include "catalog/pg_type.h"
+ #include "utils/lsyscache.h"
+ #include "utils/syscache.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
  #include "utils/rel.h"
  #include "utils/tqual.h"
  
  static int	oid_cmp(const void *p1, const void *p2);
+ static int	sort_order_cmp(const void *p1, const void *p2);
+ 
+ /*
+  * 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.
+  *
+  * Returns true iff the labels are sorted by oid after the addition.
+  */
+ 
+ bool
+ AddEnumLabel(Oid enumTypeOid, 
+ 			 char *newVal, 
+ 			 char *neighbour, 
+ 			 bool newValIsAfter,
+ 			 int  nelems,
+ 			 bool elems_are_sorted)
+ {
+ 	Oid        newOid;
+ 	Relation   pg_enum;
+ 	TupleDesc	tupDesc;
+ 	Datum		values[Natts_pg_enum];
+ 	bool		nulls[Natts_pg_enum];
+ 	NameData	enumlabel;
+ 	HeapTuple   enum_tup;
+ 	bool        result = elems_are_sorted;
+ 	int         newelemorder;
+ 
+ 	/* 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)));
+ 
+ 	/* get a new OID for the label */
+ 	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
+ 	newOid = GetNewOid(pg_enum);
+ 
+ 	if (neighbour == NULL)
+ 	{
+ 		/* 
+ 		 * Put the new label at the end of the list.
+ 		 * No change to existing tuples is required.
+ 		 */
+ 		newelemorder = nelems + 1;
+ 		/* are the elements still sorted? */
+ 		if (elems_are_sorted)
+ 		{	
+ 			CatCList   *list;
+ 			int			i;
+ 			bool        still_sorted = true;
+ 
+ 			list = SearchSysCacheList1(ENUMTYPOIDNAME, 
+ 									   ObjectIdGetDatum(enumTypeOid));
+ 			for (i = 0; i < nelems; i++)
+ 			{
+ 				HeapTuple tup = &(list->members[i]->tuple);
+ 				Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
+ 
+ 				if (en->enumsortorder == nelems)
+ 				{
+ 					if (HeapTupleGetOid(tup) > newOid)
+ 						still_sorted = false;
+ 					break;
+ 				}
+ 			}
+ 			ReleaseCatCacheList(list);
+ 			if (! still_sorted)
+ 				result = false;
+ 		}
+ 	}
+ 	else 
+ 	{
+ 		/* BEFORE or AFTER specified */
+ 		CatCList   *list;
+ 		int			i;
+ 		HeapTuple    *existing;
+ 		HeapTuple     nbr = NULL;
+ 		Form_pg_enum nbr_en;
+ 
+ 		/* get a list of the existing elements and sort them by enumsortorder */
+ 		list = SearchSysCacheList1(ENUMTYPOIDNAME, 
+ 								   ObjectIdGetDatum(enumTypeOid));
+ 		existing = palloc(nelems * sizeof(HeapTuple));
+ 
+ 		for (i = 0; i < nelems; i++)
+ 			existing[i] = &(list->members[i]->tuple);
+ 
+ 		qsort(existing, nelems, sizeof(HeapTuple), sort_order_cmp);
+ 		
+ 		/* locate the neighbour element */
+ 		for (i = 0; i < nelems; i++)
+ 		{
+ 			Form_pg_enum exists_en;
+ 			exists_en = (Form_pg_enum) GETSTRUCT(existing[i]);
+ 			if (strcmp(NameStr(exists_en->enumlabel),neighbour) == 0)
+ 				nbr = existing[i];
+ 
+ 		}
+ 
+ 		if (nbr == NULL)
+ 		{
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("\"%s\" is not an existing label.", neighbour)));
+ 		}
+ 
+ 		nbr_en = (Form_pg_enum) GETSTRUCT(nbr);
+ 
+ 		/* 
+ 		 * If BEFORE was spcified, the new label goes in the neighbour's
+ 		 * position. Otherwise, it goes in the position after that.
+ 		 */
+ 		newelemorder = nbr_en->enumsortorder;
+ 		if (newValIsAfter)
+ 			newelemorder++;
+ 
+ 		/* 
+ 		 * Add 1 to the sortorder of all the labels after where the
+ 		 * new label goes. Do it from the end back so we don't get
+ 		 * uniqueness violations.
+ 		 */
+ 		for (i = nelems - 1; i>= 0; i--)
+ 		{
+ 			HeapTuple newtup;
+ 			Form_pg_enum exst_en = (Form_pg_enum) GETSTRUCT(existing[i]);
+ 			if (exst_en->enumsortorder < newelemorder)
+ 				break;
+ 			
+ 			newtup = heap_copytuple(existing[i]);
+ 			exst_en = (Form_pg_enum) GETSTRUCT(newtup);
+ 			exst_en->enumsortorder ++;
+ 
+ 			simple_heap_update(pg_enum, &newtup->t_self, newtup);
+ 
+ 			CatalogUpdateIndexes(pg_enum, newtup);
+ 
+ 		}
+ 
+ 		ReleaseCatCacheList(list);
+ 
+ 		/* are the labels sorted by OID? */
+ 		if (result && newelemorder > 1)
+ 			result = newOid > HeapTupleGetOid(existing[newelemorder-2]);
+ 		if (result && newelemorder < nelems + 1)
+ 			result = newOid < HeapTupleGetOid(existing[newelemorder-1]);
+  
+ 	}
+ 
+ 	/* set up the new entry */
+ 	tupDesc = pg_enum->rd_att;
+ 	memset(nulls, false, sizeof(nulls));
+ 	values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
+ 	namestrcpy(&enumlabel, newVal);
+ 	values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
+ 	values[Anum_pg_enum_enumsortorder -1] =	Int32GetDatum(newelemorder);
+ 	enum_tup = heap_form_tuple(tupDesc, 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);
+ 
+ 	return result;
+ 
+ }
  
  
  /*
***************
*** 114,119 **** EnumValuesCreate(Oid enumTypeOid, List *vals,
--- 287,293 ----
  		values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
  		namestrcpy(&enumlabel, lab);
  		values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
+ 		values[Anum_pg_enum_enumsortorder -1] = Int32GetDatum(elemno+1);
  
  		tup = heap_form_tuple(tupDesc, values, nulls);
  		HeapTupleSetOid(tup, oids[elemno]);
***************
*** 177,179 **** oid_cmp(const void *p1, const void *p2)
--- 351,366 ----
  		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);
+ 
+ 	return en1->enumsortorder - en2->enumsortorder;
+ }
+ 
*** a/src/backend/catalog/pg_type.c
--- b/src/backend/catalog/pg_type.c
***************
*** 112,117 **** TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId)
--- 112,119 ----
  	values[i++] = ObjectIdGetDatum(InvalidOid); /* typbasetype */
  	values[i++] = Int32GetDatum(-1);	/* typtypmod */
  	values[i++] = Int32GetDatum(0);		/* typndims */
+ 	values[i++] = Int32GetDatum(-1);	/* typnlabels */
+ 	values[i++] = BoolGetDatum(false);	/* typsorted */
  	nulls[i++] = true;			/* typdefaultbin */
  	nulls[i++] = true;			/* typdefault */
  
***************
*** 204,210 **** TypeCreate(Oid newTypeOid,
  		   char storage,
  		   int32 typeMod,
  		   int32 typNDims,		/* Array dimensions for baseType */
! 		   bool typeNotNull)
  {
  	Relation	pg_type_desc;
  	Oid			typeObjectId;
--- 206,214 ----
  		   char storage,
  		   int32 typeMod,
  		   int32 typNDims,		/* Array dimensions for baseType */
! 		   bool typeNotNull,
! 		   int32 typeNLabels,
! 		   bool typeSorted)
  {
  	Relation	pg_type_desc;
  	Oid			typeObjectId;
***************
*** 342,347 **** TypeCreate(Oid newTypeOid,
--- 346,353 ----
  	values[i++] = ObjectIdGetDatum(baseType);	/* typbasetype */
  	values[i++] = Int32GetDatum(typeMod);		/* typtypmod */
  	values[i++] = Int32GetDatum(typNDims);		/* typndims */
+ 	values[i++] = Int32GetDatum(typeNLabels);	/* typnlabels */
+ 	values[i++] = BoolGetDatum(typeSorted);	    /* typsorted */
  
  	/*
  	 * initialize the default binary value for this type.  Check for nulls of
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
***************
*** 85,90 **** static Oid	findTypeTypmodoutFunction(List *procname);
--- 85,91 ----
  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 checkEnumOwner(HeapTuple tup, TypeName *typename);
  static char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
  					Oid baseTypeOid,
  					int typMod, Constraint *constr,
***************
*** 562,569 **** DefineType(List *names, List *parameters)
  				   storage,		/* TOAST strategy */
  				   -1,			/* typMod (Domains only) */
  				   0,			/* Array Dimensions of typbasetype */
! 				   false);		/* Type NOT NULL */
! 
  	/*
  	 * Create the array type that goes with it.
  	 */
--- 563,571 ----
  				   storage,		/* TOAST strategy */
  				   -1,			/* typMod (Domains only) */
  				   0,			/* Array Dimensions of typbasetype */
! 				   false,		/* Type NOT NULL */
! 				   -1,          /* no enum labels */
! 				   false);        /* type is not an enum, so not sorted */
  	/*
  	 * Create the array type that goes with it.
  	 */
***************
*** 601,607 **** DefineType(List *names, List *parameters)
  			   'x',				/* ARRAY is always toastable */
  			   -1,				/* typMod (Domains only) */
  			   0,				/* Array dimensions of typbasetype */
! 			   false);			/* Type NOT NULL */
  
  	pfree(array_type);
  }
--- 603,611 ----
  			   'x',				/* ARRAY is always toastable */
  			   -1,				/* typMod (Domains only) */
  			   0,				/* Array dimensions of typbasetype */
! 			   false,			/* Type NOT NULL */
! 			   -1,              /* no enum labels */
! 			   false);          /* type is not an enum, so not sorted */
  
  	pfree(array_type);
  }
***************
*** 1044,1050 **** DefineDomain(CreateDomainStmt *stmt)
  				   storage,		/* TOAST strategy */
  				   basetypeMod, /* typeMod value */
  				   typNDims,	/* Array dimensions for base type */
! 				   typNotNull); /* Type NOT NULL */
  
  	/*
  	 * Process constraints which refer to the domain ID returned by TypeCreate
--- 1048,1056 ----
  				   storage,		/* TOAST strategy */
  				   basetypeMod, /* typeMod value */
  				   typNDims,	/* Array dimensions for base type */
! 				   typNotNull,  /* Type NOT NULL */
! 				   -1,          /* no enum labels */
! 				   false);      /* type is not an enum, so not sorted */
  
  	/*
  	 * Process constraints which refer to the domain ID returned by TypeCreate
***************
*** 1094,1099 **** DefineEnum(CreateEnumStmt *stmt)
--- 1100,1108 ----
  	AclResult	aclresult;
  	Oid			old_type_oid;
  	Oid			enumArrayOid;
+ 	int         num_labels;
+ 
+ 	num_labels = list_length(stmt->vals);
  
  	/* Convert list of names to a name and namespace */
  	enumNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
***************
*** 1153,1159 **** DefineEnum(CreateEnumStmt *stmt)
  				   'p',			/* TOAST strategy always plain */
  				   -1,			/* typMod (Domains only) */
  				   0,			/* Array dimensions of typbasetype */
! 				   false);		/* Type NOT NULL */
  
  	/* Enter the enum's values into pg_enum */
  	EnumValuesCreate(enumTypeOid, stmt->vals, InvalidOid);
--- 1162,1170 ----
  				   'p',			/* TOAST strategy always plain */
  				   -1,			/* typMod (Domains only) */
  				   0,			/* Array dimensions of typbasetype */
! 				   false,		/* Type NOT NULL */
! 				   num_labels,  /* count enum labels */
! 				   true);       /* enums always start sorted */
  
  	/* Enter the enum's values into pg_enum */
  	EnumValuesCreate(enumTypeOid, stmt->vals, InvalidOid);
***************
*** 1192,1202 **** DefineEnum(CreateEnumStmt *stmt)
  			   'x',				/* ARRAY is always toastable */
  			   -1,				/* typMod (Domains only) */
  			   0,				/* Array dimensions of typbasetype */
! 			   false);			/* Type NOT NULL */
  
  	pfree(enumArrayName);
  }
  
  
  /*
   * Find suitable I/O functions for a type.
--- 1203,1290 ----
  			   'x',				/* ARRAY is always toastable */
  			   -1,				/* typMod (Domains only) */
  			   0,				/* Array dimensions of typbasetype */
! 			   false,			/* Type NOT NULL */
! 			   -1,              /* no enum labels */
! 			   false);          /* type is not an enum, so not sorted */
  
  	pfree(enumArrayName);
  }
  
+ /*
+  * AlterEnum
+  *		Registers a new label for an existing enum.
+  */
+ void
+ AlterEnum (AlterEnumStmt *stmt)
+ {
+ 	Oid			enum_type_oid;
+ 	TypeName  *typename;
+ 	bool       sorted;
+ 	HeapTuple tup, newtup;
+ 	Relation  rel;
+ 	Form_pg_type typTup;
+ 
+ 	/* Make a TypeName so we can use standard type lookup machinery */
+ 	typename = makeTypeNameFromNameList(stmt->typeName);
+ 	enum_type_oid = typenameTypeId(NULL, typename, NULL);
+ 
+ 	/* Look up the row in the type table */
+ 	rel = heap_open(TypeRelationId, RowExclusiveLock);
+ 
+ 	tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(enum_type_oid));
+ 	if (!HeapTupleIsValid(tup))
+ 		elog(ERROR, "cache lookup failed for type %u", enum_type_oid);
+ 
+ 	/* Copy the syscache entry so we can scribble on it below */
+ 	newtup = heap_copytuple(tup);
+ 	ReleaseSysCache(tup);
+ 	tup = newtup;
+ 	typTup = (Form_pg_type) GETSTRUCT(tup);
+ 
+ 	/* Check it's an enum and check user has permission to ALTER the enum */
+ 	checkEnumOwner(tup, typename);
+ 
+ 	/* Add the new label */
+ 	sorted = AddEnumLabel (enum_type_oid, stmt->newVal, 
+ 						   stmt->newValNeighbour, stmt->newValIsAfter,
+ 						   typTup->typnlabels, typTup->typsorted);
+ 
+ 	/* Update the row in pg_type */
+ 	typTup->typnlabels += 1;
+ 	typTup->typsorted = sorted;
+ 
+ 	simple_heap_update(rel, &tup->t_self, tup);
+ 
+ 	CatalogUpdateIndexes(rel, tup);
+ 
+ 	/* Clean up */
+ 	heap_close(rel, RowExclusiveLock);
+ }
+ 
+ 
+ /*
+  * 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, TypeName *typename)
+ {
+ 	Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup);
+ 
+ 	/* Check that this is actually a domain */
+ 	if (typTup->typtype != TYPTYPE_ENUM)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 				 errmsg("\"%s\" is not an enum",
+ 						TypeNameToString(typename))));
+ 
+ 	/* 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.
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2836,2841 **** _copyCreateEnumStmt(CreateEnumStmt *from)
--- 2836,2854 ----
  	return newnode;
  }
  
+ static AlterEnumStmt *
+ _copyAlterEnumStmt(AlterEnumStmt *from)
+ {
+ 	AlterEnumStmt *newnode = makeNode(AlterEnumStmt);
+ 
+ 	COPY_NODE_FIELD(typeName);
+ 	COPY_STRING_FIELD(newVal);
+ 	COPY_STRING_FIELD(newValNeighbour);
+ 	COPY_SCALAR_FIELD(newValIsAfter);
+ 
+ 	return newnode;
+ }
+ 
  static ViewStmt *
  _copyViewStmt(ViewStmt *from)
  {
***************
*** 3989,3994 **** copyObject(void *from)
--- 4002,4010 ----
  		case T_CreateEnumStmt:
  			retval = _copyCreateEnumStmt(from);
  			break;
+ 		case T_AlterEnumStmt:
+ 			retval = _copyAlterEnumStmt(from);
+ 			break;
  		case T_ViewStmt:
  			retval = _copyViewStmt(from);
  			break;
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 1375,1380 **** _equalCreateEnumStmt(CreateEnumStmt *a, CreateEnumStmt *b)
--- 1375,1391 ----
  }
  
  static bool
+ _equalAlterEnumStmt(AlterEnumStmt *a, AlterEnumStmt *b)
+ {
+ 	COMPARE_NODE_FIELD(typeName);
+ 	COMPARE_STRING_FIELD(newVal);
+ 	COMPARE_STRING_FIELD(newValNeighbour);
+ 	COMPARE_SCALAR_FIELD(newValIsAfter);
+ 
+ 	return true;
+ }
+ 
+ static bool
  _equalViewStmt(ViewStmt *a, ViewStmt *b)
  {
  	COMPARE_NODE_FIELD(view);
***************
*** 2678,2683 **** equal(void *a, void *b)
--- 2689,2697 ----
  		case T_CreateEnumStmt:
  			retval = _equalCreateEnumStmt(a, b);
  			break;
+ 		case T_AlterEnumStmt:
+ 			retval = _equalAlterEnumStmt(a, b);
+ 			break;
  		case T_ViewStmt:
  			retval = _equalViewStmt(a, b);
  			break;
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 181,188 **** static TypeName *TableFuncTypeName(List *columns);
  }
  
  %type <node>	stmt schema_stmt
! 		AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterFdwStmt
! 		AlterForeignServerStmt AlterGroupStmt
  		AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
  		AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
  		AlterRoleStmt AlterRoleSetStmt
--- 181,188 ----
  }
  
  %type <node>	stmt schema_stmt
!         AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
!         AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
  		AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
  		AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
  		AlterRoleStmt AlterRoleSetStmt
***************
*** 648,653 **** stmt :
--- 648,654 ----
  			| AlterDatabaseSetStmt
  			| AlterDefaultPrivilegesStmt
  			| AlterDomainStmt
+ 			| AlterEnumStmt
  			| AlterFdwStmt
  			| AlterForeignServerStmt
  			| AlterFunctionStmt
***************
*** 3781,3786 **** enum_val_list:	Sconst
--- 3782,3827 ----
  				{ $$ = 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->newValNeighbour = 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->newValNeighbour = $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->newValNeighbour = $7;
+ 				 n->newValIsAfter = true;
+ 
+ 				 $$ = (Node *) n;
+ 			 }
+ 		 ;
+ 
+ 
  
  /*****************************************************************************
   *
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 189,194 **** check_xact_readonly(Node *parsetree)
--- 189,195 ----
  		case T_CreateTrigStmt:
  		case T_CompositeTypeStmt:
  		case T_CreateEnumStmt:
+ 		case T_AlterEnumStmt:
  		case T_ViewStmt:
  		case T_DropCastStmt:
  		case T_DropStmt:
***************
*** 846,851 **** standard_ProcessUtility(Node *parsetree,
--- 847,856 ----
  			DefineEnum((CreateEnumStmt *) parsetree);
  			break;
  
+ 		case T_AlterEnumStmt:	/* ALTER TYPE (enum) */
+ 			AlterEnum((AlterEnumStmt *) parsetree);
+ 			break;
+ 
  		case T_ViewStmt:		/* CREATE VIEW */
  			DefineView((ViewStmt *) parsetree, queryString);
  			break;
***************
*** 1846,1851 **** CreateCommandTag(Node *parsetree)
--- 1851,1860 ----
  			tag = "CREATE TYPE";
  			break;
  
+ 		case T_AlterEnumStmt:
+ 			tag = "ALTER TYPE";
+ 			break;
+ 
  		case T_ViewStmt:
  			tag = "CREATE VIEW";
  			break;
***************
*** 2384,2389 **** GetCommandLogLevel(Node *parsetree)
--- 2393,2402 ----
  			lev = LOGSTMT_DDL;
  			break;
  
+ 		case T_AlterEnumStmt:
+ 			lev = LOGSTMT_DDL;
+ 			break;
+ 
  		case T_ViewStmt:
  			lev = LOGSTMT_DDL;
  			break;
*** a/src/backend/utils/adt/enum.c
--- b/src/backend/utils/adt/enum.c
***************
*** 14,19 ****
--- 14,20 ----
  #include "postgres.h"
  
  #include "catalog/pg_enum.h"
+ #include "catalog/pg_type.h"
  #include "fmgr.h"
  #include "utils/array.h"
  #include "utils/builtins.h"
***************
*** 22,30 ****
  #include "libpq/pqformat.h"
  #include "miscadmin.h"
  
  
  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 */
--- 23,48 ----
  #include "libpq/pqformat.h"
  #include "miscadmin.h"
  
+ typedef struct 
+ {
+ 	Oid      enum_oid;
+ 	int32   sort_order;
+ } enum_sort;
+ 
+ typedef struct 
+ {
+ 	Oid       enumtypoid;
+ 	bool      oids_are_sorted;
+ 	int       sort_list_length;
+ 	int       label_count;
+ 	enum_sort sort_order_list[1];
+ } enum_sort_cache;
+ 	
+ 
  
  static ArrayType *enum_range_internal(Oid enumtypoid, Oid lower, Oid upper);
! static int	enum_sort_cmp(const void *left, const void *right);
! static int	enum_oid_cmp(const void *left, const void *right);
  
  
  /* Basic I/O support */
***************
*** 155,167 **** enum_send(PG_FUNCTION_ARGS)
  
  /* Comparison functions and related */
  
  Datum
  enum_lt(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a < b);
  }
  
  Datum
--- 173,314 ----
  
  /* Comparison functions and related */
  
+ static inline int 
+ enum_ccmp(Oid arg1, Oid arg2, FunctionCallInfo fcinfo)
+ {
+ 
+ 	enum_sort_cache * mycache;
+ 	enum_sort *es1, *es2, srch;
+ 	int sort1, sort2;
+ 	bool added = false;
+ 	HeapTuple	enum_tup, type_tup;
+ 	Form_pg_enum en;
+ 	Oid typeoid;
+ 	Form_pg_type typ;
+ 
+ 	if (arg1 == arg2)
+ 		return 0;
+ 
+ 	mycache = (enum_sort_cache *) fcinfo->flinfo->fn_extra;
+ 	if (mycache == NULL )
+ 	{
+ 		enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg1));
+ 		en = (Form_pg_enum) GETSTRUCT(enum_tup);
+ 		typeoid = en->enumtypid;
+ 		type_tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typeoid));
+ 		typ = (Form_pg_type)  GETSTRUCT(type_tup);
+ 		if (typ->typtype != 'e')
+ 			elog(ERROR,"wrong type for oid %u",typeoid);
+         fcinfo->flinfo->fn_extra = 
+ 		  MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ 							 sizeof(enum_sort_cache) + 
+ 							 (typ->typnlabels * sizeof(enum_sort)));
+         mycache = (enum_sort_cache *) fcinfo->flinfo->fn_extra;
+ 		mycache->enumtypoid = typeoid;
+ 		mycache->sort_list_length = 1;
+ 		mycache->label_count = typ->typnlabels;
+ 		mycache->oids_are_sorted = typ->typsorted;
+ 		mycache->sort_order_list[0].enum_oid = arg1;
+ 		mycache->sort_order_list[0].sort_order = en->enumsortorder;
+ 		ReleaseSysCache(type_tup);
+ 		ReleaseSysCache(enum_tup);
+ 	}
+ 
+ 	if (mycache->oids_are_sorted)
+ 		return arg1 - arg2;
+ 
+ 	srch.enum_oid = arg1;
+ 	es1 = bsearch(&srch,
+ 				  mycache->sort_order_list,
+ 				  mycache->sort_list_length,
+ 				  sizeof(enum_sort),
+ 				  enum_oid_cmp);
+ 	srch.enum_oid = arg2;
+ 	es2 = bsearch(&srch,
+ 				  mycache->sort_order_list,
+ 				  mycache->sort_list_length,
+ 				  sizeof(enum_sort),
+ 				  enum_oid_cmp);
+ 
+ 	if (es1 == NULL)
+ 	{
+ 		
+ 		enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg1));
+ 		en = (Form_pg_enum) GETSTRUCT(enum_tup);
+ 		mycache->sort_order_list[mycache->sort_list_length].enum_oid = arg1;
+ 		sort1 = en->enumsortorder;
+ 		mycache->sort_order_list[mycache->sort_list_length].sort_order = 
+ 			sort1; 
+ 		ReleaseSysCache(enum_tup);
+ 		mycache->sort_list_length ++;
+ 		added = true;
+ 	}
+ 	else
+ 	{
+ 		/* already in cache */
+ 		sort1 = es1->sort_order;
+ 	}
+ 
+ 	if (es2 == NULL)
+ 	{
+ 		
+ 		enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg2));
+ 		en = (Form_pg_enum) GETSTRUCT(enum_tup);
+ 		sort2 = en->enumsortorder;
+ 		mycache->sort_order_list[mycache->sort_list_length].enum_oid = arg2;
+ 		mycache->sort_order_list[mycache->sort_list_length].sort_order = 
+ 			sort2;
+ 		ReleaseSysCache(enum_tup);
+ 		mycache->sort_list_length ++;
+ 		added = true;
+ 	}
+ 	else
+ 	{
+ 		/* already in cache */
+ 		sort2 = es2->sort_order;
+ 	}
+ 
+ 	if (added)
+ 	{
+ 		/* 
+ 		 * If we have more than a handful, just fetch them all, so we limit
+ 		 * the number of sort operations required.
+ 		 */
+ 		if (mycache->sort_list_length > 10 && 
+ 			mycache->sort_list_length < mycache->label_count)
+ 		{
+ 			CatCList   *nlist;
+ 			int			num, i;
+ 
+ 			nlist = SearchSysCacheList1(ENUMTYPOIDNAME, 
+ 									   ObjectIdGetDatum(mycache->enumtypoid));
+ 			num = nlist->n_members;
+ 			for (i = 0; i < num; i++)
+ 			{
+ 				HeapTuple tup = &(nlist->members[i]->tuple);
+ 				Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
+ 				
+ 				mycache->sort_order_list[i].enum_oid = HeapTupleGetOid(tup);
+ 				mycache->sort_order_list[i].sort_order = en->enumsortorder; 
+ 			}
+ 
+ 			ReleaseCatCacheList(nlist);
+ 		}
+ 
+ 		qsort(mycache->sort_order_list,mycache->sort_list_length,
+ 			  sizeof(enum_sort),enum_oid_cmp);
+ 	}
+ 
+ 	return sort1 - sort2;
+ }
+ 
  Datum
  enum_lt(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) < 0);
  }
  
  Datum
***************
*** 170,176 **** enum_le(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a <= b);
  }
  
  Datum
--- 317,323 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) <= 0);
  }
  
  Datum
***************
*** 197,203 **** enum_ge(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a >= b);
  }
  
  Datum
--- 344,350 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) >= 0);
  }
  
  Datum
***************
*** 206,212 **** enum_gt(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a > b);
  }
  
  Datum
--- 353,359 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) > 0);
  }
  
  Datum
***************
*** 215,221 **** enum_smaller(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(a <= b ? a : b);
  }
  
  Datum
--- 362,368 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(enum_ccmp(a,b,fcinfo) < 0 ? a : b);
  }
  
  Datum
***************
*** 224,230 **** enum_larger(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(a >= b ? a : b);
  }
  
  Datum
--- 371,377 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(enum_ccmp(a,b,fcinfo) > 0 ? a : b);
  }
  
  Datum
***************
*** 233,242 **** 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)
  		PG_RETURN_INT32(0);
  	else
  		PG_RETURN_INT32(-1);
  }
--- 380,389 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	if (a == b)
  		PG_RETURN_INT32(0);
+ 	else if (enum_ccmp(a,b,fcinfo) > 0)
+ 		PG_RETURN_INT32(1);
  	else
  		PG_RETURN_INT32(-1);
  }
***************
*** 248,253 **** enum_first(PG_FUNCTION_ARGS)
--- 395,401 ----
  {
  	Oid			enumtypoid;
  	Oid			min = InvalidOid;
+ 	int         min_sort = -1; /* value will never in fact be used */
  	CatCList   *list;
  	int			num,
  				i;
***************
*** 267,276 **** enum_first(PG_FUNCTION_ARGS)
  	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);
--- 415,428 ----
  	num = list->n_members;
  	for (i = 0; i < num; i++)
  	{
! 		HeapTuple tup = &(list->members[i]->tuple);
! 		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
! 
! 		if (!OidIsValid(min) || en->enumsortorder < min_sort)
! 		{
! 			min = HeapTupleGetOid(tup);
! 			min_sort = en->enumsortorder;
! 		}
  	}
  
  	ReleaseCatCacheList(list);
***************
*** 287,292 **** enum_last(PG_FUNCTION_ARGS)
--- 439,445 ----
  {
  	Oid			enumtypoid;
  	Oid			max = InvalidOid;
+ 	int         max_sort = -1; /* value will never in fact be used */
  	CatCList   *list;
  	int			num,
  				i;
***************
*** 306,315 **** enum_last(PG_FUNCTION_ARGS)
  	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);
--- 459,472 ----
  	num = list->n_members;
  	for (i = 0; i < num; i++)
  	{
! 		HeapTuple tup = &(list->members[i]->tuple);
! 		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
! 
! 		if (!OidIsValid(max) || en->enumsortorder > max_sort)
! 		{
! 			max = HeapTupleGetOid(tup);
! 			max_sort = en->enumsortorder;
! 		}
  	}
  
  	ReleaseCatCacheList(list);
***************
*** 382,427 **** enum_range_internal(Oid enumtypoid, Oid lower, Oid upper)
  				i,
  				j;
  	Datum	   *elems;
  
  	list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid));
  	total = list->n_members;
  
  	elems = (Datum *) palloc(total * sizeof(Datum));
  
- 	j = 0;
  	for (i = 0; i < total; i++)
  	{
! 		Oid			val = HeapTupleGetOid(&(list->members[i]->tuple));
  
- 		if ((!OidIsValid(lower) || lower <= val) &&
- 			(!OidIsValid(upper) || val <= upper))
- 			elems[j++] = ObjectIdGetDatum(val);
  	}
  
  	/* shouldn't need the cache anymore */
  	ReleaseCatCacheList(list);
  
! 	/* sort results into OID order */
! 	qsort(elems, j, sizeof(Datum), enum_elem_cmp);
  
  	/* note this hardwires some details about the representation of Oid */
  	result = construct_array(elems, j, 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;
  }
--- 539,614 ----
  				i,
  				j;
  	Datum	   *elems;
+ 	enum_sort  *sort_items;
+ 	bool        left_found;
  
  	list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid));
  	total = list->n_members;
  
  	elems = (Datum *) palloc(total * sizeof(Datum));
+ 	sort_items = (enum_sort *) palloc(total * sizeof(enum_sort));
  
  	for (i = 0; i < total; i++)
  	{
! 		HeapTuple tup = &(list->members[i]->tuple);
! 		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
! 
! 		sort_items[i].enum_oid = HeapTupleGetOid(tup);
! 		sort_items[i].sort_order = en->enumsortorder;
  
  	}
  
  	/* shouldn't need the cache anymore */
  	ReleaseCatCacheList(list);
  
! 	/* sort results into sort_order sequence */
! 	qsort(sort_items, total, sizeof(enum_sort), enum_sort_cmp);
! 
! 	j = 0; 
! 	left_found = !OidIsValid(lower);
! 	for (i=0; i < total; i++)
! 	{
! 		if (! left_found && lower == sort_items[i].enum_oid)
! 			left_found = true;
! 
! 		if (left_found)
! 			elems[j++] = ObjectIdGetDatum(sort_items[i].enum_oid);
! 
! 		if (OidIsValid(upper) && upper == sort_items[i].enum_oid)
! 			break;
! 	}
  
  	/* note this hardwires some details about the representation of Oid */
  	result = construct_array(elems, j, enumtypoid, sizeof(Oid), true, 'i');
  
  	pfree(elems);
+ 	pfree(sort_items);
  
  	return result;
  }
  
! /* 
!  * qsort comparison using sort order, for range routines 
!  */
  static int
! enum_sort_cmp(const void *left, const void *right)
  {
! 	enum_sort  *l = (enum_sort *) left;
! 	enum_sort  *r = (enum_sort *) right;
! 
! 	return l->sort_order - r->sort_order;
  }
+ 
+ /* 
+  * qsort comparison using OID order for comparison search cache
+  */
+ static int 
+ enum_oid_cmp(const void *es1, const void *es2)
+ {
+ 	enum_sort *p1, *p2;
+ 	p1 = (enum_sort *)es1;
+ 	p2 = (enum_sort *)es2;
+ 	return p1->enum_oid - p2->enum_oid;
+ }
+ 
+ 
*** a/src/include/catalog/indexing.h
--- b/src/include/catalog/indexing.h
***************
*** 147,152 **** DECLARE_UNIQUE_INDEX(pg_enum_oid_index, 3502, on pg_enum using btree(oid oid_ops
--- 147,154 ----
  #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, 3539, on pg_enum using btree(enumtypid oid_ops, enumsortorder int4_ops));
+ #define EnumTypIdSortOrderIndexId 3539
  
  /* 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));
*** a/src/include/catalog/pg_class.h
--- b/src/include/catalog/pg_class.h
***************
*** 132,138 **** typedef FormData_pg_class *Form_pg_class;
   */
  
  /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
! DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f f r 28 0 t f f f f f 3 _null_ _null_ ));
  DESCR("");
  DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f f r 19 0 f f f f f f 3 _null_ _null_ ));
  DESCR("");
--- 132,138 ----
   */
  
  /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
! DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f f r 30 0 t f f f f f 3 _null_ _null_ ));
  DESCR("");
  DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f f r 19 0 f f f f f f 3 _null_ _null_ ));
  DESCR("");
*** a/src/include/catalog/pg_enum.h
--- b/src/include/catalog/pg_enum.h
***************
*** 35,40 **** CATALOG(pg_enum,3501)
--- 35,41 ----
  {
  	Oid			enumtypid;		/* OID of owning enum type */
  	NameData	enumlabel;		/* text representation of enum value */
+ 	int4        enumsortorder;  /* sort order for this enum label */
  } FormData_pg_enum;
  
  /* ----------------
***************
*** 48,56 **** typedef FormData_pg_enum *Form_pg_enum;
   *		compiler constants for pg_enum
   * ----------------
   */
! #define Natts_pg_enum					2
  #define Anum_pg_enum_enumtypid			1
  #define Anum_pg_enum_enumlabel			2
  
  /* ----------------
   *		pg_enum has no initial contents
--- 49,58 ----
   *		compiler constants for pg_enum
   * ----------------
   */
! #define Natts_pg_enum					3
  #define Anum_pg_enum_enumtypid			1
  #define Anum_pg_enum_enumlabel			2
+ #define Anum_pg_enum_enumsortorder		3
  
  /* ----------------
   *		pg_enum has no initial contents
***************
*** 63,67 **** typedef FormData_pg_enum *Form_pg_enum;
--- 65,71 ----
  extern void EnumValuesCreate(Oid enumTypeOid, List *vals,
  				 Oid binary_upgrade_next_pg_enum_oid);
  extern void EnumValuesDelete(Oid enumTypeOid);
+ extern bool AddEnumLabel(Oid enumTypeOid, char *newVal, char *neighbour, 
+ 						 bool newValIsAfter, int nelems, bool elems_are_sorted);
  
  #endif   /* PG_ENUM_H */
*** a/src/include/catalog/pg_type.h
--- b/src/include/catalog/pg_type.h
***************
*** 194,199 **** CATALOG(pg_type,1247) BKI_BOOTSTRAP BKI_ROWTYPE_OID(71) BKI_SCHEMA_MACRO
--- 194,211 ----
  	 */
  	int4		typndims;
  
+     /* 
+      * typnlabels, contains a count on the number of labels an enum type has, 
+      * -1 for a non-enum type.
+      */
+     int4          typnlabels;
+ 
+     /*
+      * typsorted is true if the oids of an enum type reflect the type's sort
+      * order, false otherwise including for a non-enum type.
+      */
+     bool         typsorted;
+ 
  	/*
  	 * If typdefaultbin is not NULL, it is the nodeToString representation of
  	 * a default expression for the type.  Currently this is only used for
***************
*** 224,230 **** typedef FormData_pg_type *Form_pg_type;
   *		compiler constants for pg_type
   * ----------------
   */
! #define Natts_pg_type					28
  #define Anum_pg_type_typname			1
  #define Anum_pg_type_typnamespace		2
  #define Anum_pg_type_typowner			3
--- 236,242 ----
   *		compiler constants for pg_type
   * ----------------
   */
! #define Natts_pg_type					30
  #define Anum_pg_type_typname			1
  #define Anum_pg_type_typnamespace		2
  #define Anum_pg_type_typowner			3
***************
*** 251,258 **** typedef FormData_pg_type *Form_pg_type;
  #define Anum_pg_type_typbasetype		24
  #define Anum_pg_type_typtypmod			25
  #define Anum_pg_type_typndims			26
! #define Anum_pg_type_typdefaultbin		27
! #define Anum_pg_type_typdefault			28
  
  
  /* ----------------
--- 263,272 ----
  #define Anum_pg_type_typbasetype		24
  #define Anum_pg_type_typtypmod			25
  #define Anum_pg_type_typndims			26
! #define Anum_pg_type_typnlabels			27
! #define Anum_pg_type_typsorted			28
! #define Anum_pg_type_typdefaultbin		29
! #define Anum_pg_type_typdefault			30
  
  
  /* ----------------
***************
*** 269,351 **** typedef FormData_pg_type *Form_pg_type;
   */
  
  /* OIDS 1 - 99 */
! DATA(insert OID = 16 (	bool	   PGNSP PGUID	1 t b B t t \054 0	 0 1000 boolin boolout boolrecv boolsend - - - c p f 0 -1 0 _null_ _null_ ));
  DESCR("boolean, 'true'/'false'");
  #define BOOLOID			16
  
! DATA(insert OID = 17 (	bytea	   PGNSP PGUID -1 f b U f t \054 0	0 1001 byteain byteaout bytearecv byteasend - - - i x f 0 -1 0 _null_ _null_ ));
  DESCR("variable-length string, binary values escaped");
  #define BYTEAOID		17
  
! DATA(insert OID = 18 (	char	   PGNSP PGUID	1 t b S f t \054 0	 0 1002 charin charout charrecv charsend - - - c p f 0 -1 0 _null_ _null_ ));
  DESCR("single character");
  #define CHAROID			18
  
! DATA(insert OID = 19 (	name	   PGNSP PGUID NAMEDATALEN f b S f t \054 0 18 1003 namein nameout namerecv namesend - - - c p f 0 -1 0 _null_ _null_ ));
  DESCR("63-character type for storing system identifiers");
  #define NAMEOID			19
  
! DATA(insert OID = 20 (	int8	   PGNSP PGUID	8 FLOAT8PASSBYVAL b N f t \054 0	 0 1016 int8in int8out int8recv int8send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("~18 digit integer, 8-byte storage");
  #define INT8OID			20
  
! DATA(insert OID = 21 (	int2	   PGNSP PGUID	2 t b N f t \054 0	 0 1005 int2in int2out int2recv int2send - - - s p f 0 -1 0 _null_ _null_ ));
  DESCR("-32 thousand to 32 thousand, 2-byte storage");
  #define INT2OID			21
  
! DATA(insert OID = 22 (	int2vector PGNSP PGUID -1 f b A f t \054 0	21 1006 int2vectorin int2vectorout int2vectorrecv int2vectorsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("array of int2, used in system tables");
  #define INT2VECTOROID	22
  
! DATA(insert OID = 23 (	int4	   PGNSP PGUID	4 t b N f t \054 0	 0 1007 int4in int4out int4recv int4send - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("-2 billion to 2 billion integer, 4-byte storage");
  #define INT4OID			23
  
! DATA(insert OID = 24 (	regproc    PGNSP PGUID	4 t b N f t \054 0	 0 1008 regprocin regprocout regprocrecv regprocsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered procedure");
  #define REGPROCOID		24
  
! DATA(insert OID = 25 (	text	   PGNSP PGUID -1 f b S t t \054 0	0 1009 textin textout textrecv textsend - - - i x f 0 -1 0 _null_ _null_ ));
  DESCR("variable-length string, no limit specified");
  #define TEXTOID			25
  
! DATA(insert OID = 26 (	oid		   PGNSP PGUID	4 t b N t t \054 0	 0 1028 oidin oidout oidrecv oidsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("object identifier(oid), maximum 4 billion");
  #define OIDOID			26
  
! DATA(insert OID = 27 (	tid		   PGNSP PGUID	6 f b U f t \054 0	 0 1010 tidin tidout tidrecv tidsend - - - s p f 0 -1 0 _null_ _null_ ));
  DESCR("(block, offset), physical location of tuple");
  #define TIDOID		27
  
! DATA(insert OID = 28 (	xid		   PGNSP PGUID	4 t b U f t \054 0	 0 1011 xidin xidout xidrecv xidsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("transaction id");
  #define XIDOID 28
  
! DATA(insert OID = 29 (	cid		   PGNSP PGUID	4 t b U f t \054 0	 0 1012 cidin cidout cidrecv cidsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("command identifier type, sequence in transaction id");
  #define CIDOID 29
  
! DATA(insert OID = 30 (	oidvector  PGNSP PGUID -1 f b A f t \054 0	26 1013 oidvectorin oidvectorout oidvectorrecv oidvectorsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("array of oids, used in system tables");
  #define OIDVECTOROID	30
  
  /* hand-built rowtype entries for bootstrapped catalogs */
  /* NB: OIDs assigned here must match the BKI_ROWTYPE_OID declarations */
  
! DATA(insert OID = 71 (	pg_type			PGNSP PGUID -1 f c C f t \054 1247 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 75 (	pg_attribute	PGNSP PGUID -1 f c C f t \054 1249 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 81 (	pg_proc			PGNSP PGUID -1 f c C f t \054 1255 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 83 (	pg_class		PGNSP PGUID -1 f c C f t \054 1259 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 100 - 199 */
! DATA(insert OID = 142 ( xml		   PGNSP PGUID -1 f b U f t \054 0 0 143 xml_in xml_out xml_recv xml_send - - - i x f 0 -1 0 _null_ _null_ ));
  DESCR("XML content");
  #define XMLOID 142
! DATA(insert OID = 143 ( _xml	   PGNSP PGUID -1 f b A f t \054 0 142 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 200 - 299 */
  
! DATA(insert OID = 210 (  smgr	   PGNSP PGUID 2 t b U f t \054 0 0 0 smgrin smgrout - - - - - s p f 0 -1 0 _null_ _null_ ));
  DESCR("storage manager");
  
  /* OIDS 300 - 399 */
--- 283,365 ----
   */
  
  /* OIDS 1 - 99 */
! DATA(insert OID = 16 (	bool	   PGNSP PGUID	1 t b B t t \054 0	 0 1000 boolin boolout boolrecv boolsend - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("boolean, 'true'/'false'");
  #define BOOLOID			16
  
! DATA(insert OID = 17 (	bytea	   PGNSP PGUID -1 f b U f t \054 0	0 1001 byteain byteaout bytearecv byteasend - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("variable-length string, binary values escaped");
  #define BYTEAOID		17
  
! DATA(insert OID = 18 (	char	   PGNSP PGUID	1 t b S f t \054 0	 0 1002 charin charout charrecv charsend - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("single character");
  #define CHAROID			18
  
! DATA(insert OID = 19 (	name	   PGNSP PGUID NAMEDATALEN f b S f t \054 0 18 1003 namein nameout namerecv namesend - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("63-character type for storing system identifiers");
  #define NAMEOID			19
  
! DATA(insert OID = 20 (	int8	   PGNSP PGUID	8 FLOAT8PASSBYVAL b N f t \054 0	 0 1016 int8in int8out int8recv int8send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("~18 digit integer, 8-byte storage");
  #define INT8OID			20
  
! DATA(insert OID = 21 (	int2	   PGNSP PGUID	2 t b N f t \054 0	 0 1005 int2in int2out int2recv int2send - - - s p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("-32 thousand to 32 thousand, 2-byte storage");
  #define INT2OID			21
  
! DATA(insert OID = 22 (	int2vector PGNSP PGUID -1 f b A f t \054 0	21 1006 int2vectorin int2vectorout int2vectorrecv int2vectorsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("array of int2, used in system tables");
  #define INT2VECTOROID	22
  
! DATA(insert OID = 23 (	int4	   PGNSP PGUID	4 t b N f t \054 0	 0 1007 int4in int4out int4recv int4send - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("-2 billion to 2 billion integer, 4-byte storage");
  #define INT4OID			23
  
! DATA(insert OID = 24 (	regproc    PGNSP PGUID	4 t b N f t \054 0	 0 1008 regprocin regprocout regprocrecv regprocsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered procedure");
  #define REGPROCOID		24
  
! DATA(insert OID = 25 (	text	   PGNSP PGUID -1 f b S t t \054 0	0 1009 textin textout textrecv textsend - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("variable-length string, no limit specified");
  #define TEXTOID			25
  
! DATA(insert OID = 26 (	oid		   PGNSP PGUID	4 t b N t t \054 0	 0 1028 oidin oidout oidrecv oidsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("object identifier(oid), maximum 4 billion");
  #define OIDOID			26
  
! DATA(insert OID = 27 (	tid		   PGNSP PGUID	6 f b U f t \054 0	 0 1010 tidin tidout tidrecv tidsend - - - s p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("(block, offset), physical location of tuple");
  #define TIDOID		27
  
! DATA(insert OID = 28 (	xid		   PGNSP PGUID	4 t b U f t \054 0	 0 1011 xidin xidout xidrecv xidsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("transaction id");
  #define XIDOID 28
  
! DATA(insert OID = 29 (	cid		   PGNSP PGUID	4 t b U f t \054 0	 0 1012 cidin cidout cidrecv cidsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("command identifier type, sequence in transaction id");
  #define CIDOID 29
  
! DATA(insert OID = 30 (	oidvector  PGNSP PGUID -1 f b A f t \054 0	26 1013 oidvectorin oidvectorout oidvectorrecv oidvectorsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("array of oids, used in system tables");
  #define OIDVECTOROID	30
  
  /* hand-built rowtype entries for bootstrapped catalogs */
  /* NB: OIDs assigned here must match the BKI_ROWTYPE_OID declarations */
  
! DATA(insert OID = 71 (	pg_type			PGNSP PGUID -1 f c C f t \054 1247 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 75 (	pg_attribute	PGNSP PGUID -1 f c C f t \054 1249 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 81 (	pg_proc			PGNSP PGUID -1 f c C f t \054 1255 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 83 (	pg_class		PGNSP PGUID -1 f c C f t \054 1259 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 100 - 199 */
! DATA(insert OID = 142 ( xml		   PGNSP PGUID -1 f b U f t \054 0 0 143 xml_in xml_out xml_recv xml_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("XML content");
  #define XMLOID 142
! DATA(insert OID = 143 ( _xml	   PGNSP PGUID -1 f b A f t \054 0 142 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 200 - 299 */
  
! DATA(insert OID = 210 (  smgr	   PGNSP PGUID 2 t b U f t \054 0 0 0 smgrin smgrout - - - - - s p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("storage manager");
  
  /* OIDS 300 - 399 */
***************
*** 355,585 **** DESCR("storage manager");
  /* OIDS 500 - 599 */
  
  /* OIDS 600 - 699 */
! DATA(insert OID = 600 (  point	   PGNSP PGUID 16 f b G f t \054 0 701 1017 point_in point_out point_recv point_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("geometric point '(x, y)'");
  #define POINTOID		600
! DATA(insert OID = 601 (  lseg	   PGNSP PGUID 32 f b G f t \054 0 600 1018 lseg_in lseg_out lseg_recv lseg_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("geometric line segment '(pt1,pt2)'");
  #define LSEGOID			601
! DATA(insert OID = 602 (  path	   PGNSP PGUID -1 f b G f t \054 0 0 1019 path_in path_out path_recv path_send - - - d x f 0 -1 0 _null_ _null_ ));
  DESCR("geometric path '(pt1,...)'");
  #define PATHOID			602
! DATA(insert OID = 603 (  box	   PGNSP PGUID 32 f b G f t \073 0 600 1020 box_in box_out box_recv box_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("geometric box '(lower left,upper right)'");
  #define BOXOID			603
! DATA(insert OID = 604 (  polygon   PGNSP PGUID -1 f b G f t \054 0	 0 1027 poly_in poly_out poly_recv poly_send - - - d x f 0 -1 0 _null_ _null_ ));
  DESCR("geometric polygon '(pt1,...)'");
  #define POLYGONOID		604
  
! DATA(insert OID = 628 (  line	   PGNSP PGUID 32 f b G f t \054 0 701 629 line_in line_out line_recv line_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("geometric line (not implemented)");
  #define LINEOID			628
! DATA(insert OID = 629 (  _line	   PGNSP PGUID	-1 f b A f t \054 0 628 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
  DESCR("");
  
  /* OIDS 700 - 799 */
  
! DATA(insert OID = 700 (  float4    PGNSP PGUID	4 FLOAT4PASSBYVAL b N f t \054 0	 0 1021 float4in float4out float4recv float4send - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("single-precision floating point number, 4-byte storage");
  #define FLOAT4OID 700
! DATA(insert OID = 701 (  float8    PGNSP PGUID	8 FLOAT8PASSBYVAL b N t t \054 0	 0 1022 float8in float8out float8recv float8send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("double-precision floating point number, 8-byte storage");
  #define FLOAT8OID 701
! DATA(insert OID = 702 (  abstime   PGNSP PGUID	4 t b D f t \054 0	 0 1023 abstimein abstimeout abstimerecv abstimesend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("absolute, limited-range date and time (Unix system time)");
  #define ABSTIMEOID		702
! DATA(insert OID = 703 (  reltime   PGNSP PGUID	4 t b T f t \054 0	 0 1024 reltimein reltimeout reltimerecv reltimesend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("relative, limited-range time interval (Unix delta time)");
  #define RELTIMEOID		703
! DATA(insert OID = 704 (  tinterval PGNSP PGUID 12 f b T f t \054 0	 0 1025 tintervalin tintervalout tintervalrecv tintervalsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("(abstime,abstime), time interval");
  #define TINTERVALOID	704
! DATA(insert OID = 705 (  unknown   PGNSP PGUID -2 f b X f t \054 0	 0 0 unknownin unknownout unknownrecv unknownsend - - - c p f 0 -1 0 _null_ _null_ ));
  DESCR("");
  #define UNKNOWNOID		705
  
! DATA(insert OID = 718 (  circle    PGNSP PGUID	24 f b G f t \054 0 0 719 circle_in circle_out circle_recv circle_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("geometric circle '(center,radius)'");
  #define CIRCLEOID		718
! DATA(insert OID = 719 (  _circle   PGNSP PGUID	-1 f b A f t \054 0  718 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 790 (  money	   PGNSP PGUID	 8 FLOAT8PASSBYVAL b N f t \054 0 0 791 cash_in cash_out cash_recv cash_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("monetary amounts, $d,ddd.cc");
  #define CASHOID 790
! DATA(insert OID = 791 (  _money    PGNSP PGUID	-1 f b A f t \054 0  790 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 800 - 899 */
! DATA(insert OID = 829 ( macaddr    PGNSP PGUID	6 f b U f t \054 0 0 1040 macaddr_in macaddr_out macaddr_recv macaddr_send - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("XX:XX:XX:XX:XX:XX, MAC address");
  #define MACADDROID 829
! DATA(insert OID = 869 ( inet	   PGNSP PGUID	-1 f b I t t \054 0 0 1041 inet_in inet_out inet_recv inet_send - - - i m f 0 -1 0 _null_ _null_ ));
  DESCR("IP address/netmask, host address, netmask optional");
  #define INETOID 869
! DATA(insert OID = 650 ( cidr	   PGNSP PGUID	-1 f b I f t \054 0 0 651 cidr_in cidr_out cidr_recv cidr_send - - - i m f 0 -1 0 _null_ _null_ ));
  DESCR("network IP address/netmask, network address");
  #define CIDROID 650
  
  /* OIDS 900 - 999 */
  
  /* OIDS 1000 - 1099 */
! DATA(insert OID = 1000 (  _bool		 PGNSP PGUID -1 f b A f t \054 0	16 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1001 (  _bytea	 PGNSP PGUID -1 f b A f t \054 0	17 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1002 (  _char		 PGNSP PGUID -1 f b A f t \054 0	18 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1003 (  _name		 PGNSP PGUID -1 f b A f t \054 0	19 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1005 (  _int2		 PGNSP PGUID -1 f b A f t \054 0	21 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1006 (  _int2vector PGNSP PGUID -1 f b A f t \054 0	22 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1007 (  _int4		 PGNSP PGUID -1 f b A f t \054 0	23 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  #define INT4ARRAYOID		1007
! DATA(insert OID = 1008 (  _regproc	 PGNSP PGUID -1 f b A f t \054 0	24 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1009 (  _text		 PGNSP PGUID -1 f b A f t \054 0	25 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  #define TEXTARRAYOID		1009
! DATA(insert OID = 1028 (  _oid		 PGNSP PGUID -1 f b A f t \054 0	26 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1010 (  _tid		 PGNSP PGUID -1 f b A f t \054 0	27 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1011 (  _xid		 PGNSP PGUID -1 f b A f t \054 0	28 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1012 (  _cid		 PGNSP PGUID -1 f b A f t \054 0	29 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1013 (  _oidvector PGNSP PGUID -1 f b A f t \054 0	30 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1014 (  _bpchar	 PGNSP PGUID -1 f b A f t \054 0 1042 0 array_in array_out array_recv array_send bpchartypmodin bpchartypmodout - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1015 (  _varchar	 PGNSP PGUID -1 f b A f t \054 0 1043 0 array_in array_out array_recv array_send varchartypmodin varchartypmodout - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1016 (  _int8		 PGNSP PGUID -1 f b A f t \054 0	20 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1017 (  _point	 PGNSP PGUID -1 f b A f t \054 0 600 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1018 (  _lseg		 PGNSP PGUID -1 f b A f t \054 0 601 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1019 (  _path		 PGNSP PGUID -1 f b A f t \054 0 602 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1020 (  _box		 PGNSP PGUID -1 f b A f t \073 0 603 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1021 (  _float4	 PGNSP PGUID -1 f b A f t \054 0 700 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  #define FLOAT4ARRAYOID 1021
! DATA(insert OID = 1022 (  _float8	 PGNSP PGUID -1 f b A f t \054 0 701 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1023 (  _abstime	 PGNSP PGUID -1 f b A f t \054 0 702 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1024 (  _reltime	 PGNSP PGUID -1 f b A f t \054 0 703 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1025 (  _tinterval PGNSP PGUID -1 f b A f t \054 0 704 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1027 (  _polygon	 PGNSP PGUID -1 f b A f t \054 0 604 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1033 (  aclitem	 PGNSP PGUID 12 f b U f t \054 0 0 1034 aclitemin aclitemout - - - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("access control list");
  #define ACLITEMOID		1033
! DATA(insert OID = 1034 (  _aclitem	 PGNSP PGUID -1 f b A f t \054 0 1033 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1040 (  _macaddr	 PGNSP PGUID -1 f b A f t \054 0  829 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1041 (  _inet		 PGNSP PGUID -1 f b A f t \054 0  869 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 651  (  _cidr		 PGNSP PGUID -1 f b A f t \054 0  650 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1263 (  _cstring	 PGNSP PGUID -1 f b A f t \054 0 2275 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  #define CSTRINGARRAYOID		1263
  
! DATA(insert OID = 1042 ( bpchar		 PGNSP PGUID -1 f b S f t \054 0	0 1014 bpcharin bpcharout bpcharrecv bpcharsend bpchartypmodin bpchartypmodout - i x f 0 -1 0 _null_ _null_ ));
  DESCR("char(length), blank-padded string, fixed storage length");
  #define BPCHAROID		1042
! DATA(insert OID = 1043 ( varchar	 PGNSP PGUID -1 f b S f t \054 0	0 1015 varcharin varcharout varcharrecv varcharsend varchartypmodin varchartypmodout - i x f 0 -1 0 _null_ _null_ ));
  DESCR("varchar(length), non-blank-padded string, variable storage length");
  #define VARCHAROID		1043
  
! DATA(insert OID = 1082 ( date		 PGNSP PGUID	4 t b D f t \054 0	0 1182 date_in date_out date_recv date_send - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("date");
  #define DATEOID			1082
! DATA(insert OID = 1083 ( time		 PGNSP PGUID	8 FLOAT8PASSBYVAL b D f t \054 0	0 1183 time_in time_out time_recv time_send timetypmodin timetypmodout - d p f 0 -1 0 _null_ _null_ ));
  DESCR("time of day");
  #define TIMEOID			1083
  
  /* OIDS 1100 - 1199 */
! DATA(insert OID = 1114 ( timestamp	 PGNSP PGUID	8 FLOAT8PASSBYVAL b D f t \054 0	0 1115 timestamp_in timestamp_out timestamp_recv timestamp_send timestamptypmodin timestamptypmodout - d p f 0 -1 0 _null_ _null_ ));
  DESCR("date and time");
  #define TIMESTAMPOID	1114
! DATA(insert OID = 1115 ( _timestamp  PGNSP PGUID	-1 f b A f t \054 0 1114 0 array_in array_out array_recv array_send timestamptypmodin timestamptypmodout - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1182 ( _date		 PGNSP PGUID	-1 f b A f t \054 0 1082 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1183 ( _time		 PGNSP PGUID	-1 f b A f t \054 0 1083 0 array_in array_out array_recv array_send timetypmodin timetypmodout - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1184 ( timestamptz PGNSP PGUID	8 FLOAT8PASSBYVAL b D t t \054 0	0 1185 timestamptz_in timestamptz_out timestamptz_recv timestamptz_send timestamptztypmodin timestamptztypmodout - d p f 0 -1 0 _null_ _null_ ));
  DESCR("date and time with time zone");
  #define TIMESTAMPTZOID	1184
! DATA(insert OID = 1185 ( _timestamptz PGNSP PGUID -1 f b A f t \054 0	1184 0 array_in array_out array_recv array_send timestamptztypmodin timestamptztypmodout - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1186 ( interval	 PGNSP PGUID 16 f b T t t \054 0	0 1187 interval_in interval_out interval_recv interval_send intervaltypmodin intervaltypmodout - d p f 0 -1 0 _null_ _null_ ));
  DESCR("@ <number> <units>, time interval");
  #define INTERVALOID		1186
! DATA(insert OID = 1187 ( _interval	 PGNSP PGUID	-1 f b A f t \054 0 1186 0 array_in array_out array_recv array_send intervaltypmodin intervaltypmodout - d x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 1200 - 1299 */
! DATA(insert OID = 1231 (  _numeric	 PGNSP PGUID -1 f b A f t \054 0	1700 0 array_in array_out array_recv array_send numerictypmodin numerictypmodout - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1266 ( timetz		 PGNSP PGUID 12 f b D f t \054 0	0 1270 timetz_in timetz_out timetz_recv timetz_send timetztypmodin timetztypmodout - d p f 0 -1 0 _null_ _null_ ));
  DESCR("time of day with time zone");
  #define TIMETZOID		1266
! DATA(insert OID = 1270 ( _timetz	 PGNSP PGUID -1 f b A f t \054 0	1266 0 array_in array_out array_recv array_send timetztypmodin timetztypmodout - d x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 1500 - 1599 */
! DATA(insert OID = 1560 ( bit		 PGNSP PGUID -1 f b V f t \054 0	0 1561 bit_in bit_out bit_recv bit_send bittypmodin bittypmodout - i x f 0 -1 0 _null_ _null_ ));
  DESCR("fixed-length bit string");
  #define BITOID	 1560
! DATA(insert OID = 1561 ( _bit		 PGNSP PGUID -1 f b A f t \054 0	1560 0 array_in array_out array_recv array_send bittypmodin bittypmodout - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1562 ( varbit		 PGNSP PGUID -1 f b V t t \054 0	0 1563 varbit_in varbit_out varbit_recv varbit_send varbittypmodin varbittypmodout - i x f 0 -1 0 _null_ _null_ ));
  DESCR("variable-length bit string");
  #define VARBITOID	  1562
! DATA(insert OID = 1563 ( _varbit	 PGNSP PGUID -1 f b A f t \054 0	1562 0 array_in array_out array_recv array_send varbittypmodin varbittypmodout - i x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 1600 - 1699 */
  
  /* OIDS 1700 - 1799 */
! DATA(insert OID = 1700 ( numeric	   PGNSP PGUID -1 f b N f t \054 0	0 1231 numeric_in numeric_out numeric_recv numeric_send numerictypmodin numerictypmodout - i m f 0 -1 0 _null_ _null_ ));
  DESCR("numeric(precision, decimal), arbitrary precision number");
  #define NUMERICOID		1700
  
! DATA(insert OID = 1790 ( refcursor	   PGNSP PGUID -1 f b U f t \054 0	0 2201 textin textout textrecv textsend - - - i x f 0 -1 0 _null_ _null_ ));
  DESCR("reference to cursor (portal name)");
  #define REFCURSOROID	1790
  
  /* OIDS 2200 - 2299 */
! DATA(insert OID = 2201 ( _refcursor    PGNSP PGUID -1 f b A f t \054 0 1790 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  
! DATA(insert OID = 2202 ( regprocedure  PGNSP PGUID	4 t b N f t \054 0	 0 2207 regprocedurein regprocedureout regprocedurerecv regproceduresend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered procedure (with args)");
  #define REGPROCEDUREOID 2202
  
! DATA(insert OID = 2203 ( regoper	   PGNSP PGUID	4 t b N f t \054 0	 0 2208 regoperin regoperout regoperrecv regopersend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered operator");
  #define REGOPEROID		2203
  
! DATA(insert OID = 2204 ( regoperator   PGNSP PGUID	4 t b N f t \054 0	 0 2209 regoperatorin regoperatorout regoperatorrecv regoperatorsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered operator (with args)");
  #define REGOPERATOROID	2204
  
! DATA(insert OID = 2205 ( regclass	   PGNSP PGUID	4 t b N f t \054 0	 0 2210 regclassin regclassout regclassrecv regclasssend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered class");
  #define REGCLASSOID		2205
  
! DATA(insert OID = 2206 ( regtype	   PGNSP PGUID	4 t b N f t \054 0	 0 2211 regtypein regtypeout regtyperecv regtypesend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered type");
  #define REGTYPEOID		2206
  
! DATA(insert OID = 2207 ( _regprocedure PGNSP PGUID -1 f b A f t \054 0 2202 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 2208 ( _regoper	   PGNSP PGUID -1 f b A f t \054 0 2203 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 2209 ( _regoperator  PGNSP PGUID -1 f b A f t \054 0 2204 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 2210 ( _regclass	   PGNSP PGUID -1 f b A f t \054 0 2205 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 2211 ( _regtype	   PGNSP PGUID -1 f b A f t \054 0 2206 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  #define REGTYPEARRAYOID 2211
  
  /* uuid */
! DATA(insert OID = 2950 ( uuid			PGNSP PGUID 16 f b U f t \054 0 0 2951 uuid_in uuid_out uuid_recv uuid_send - - - c p f 0 -1 0 _null_ _null_ ));
  DESCR("UUID datatype");
! DATA(insert OID = 2951 ( _uuid			PGNSP PGUID -1 f b A f t \054 0 2950 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  
  /* text search */
! DATA(insert OID = 3614 ( tsvector		PGNSP PGUID -1 f b U f t \054 0 0 3643 tsvectorin tsvectorout tsvectorrecv tsvectorsend - - ts_typanalyze i x f 0 -1 0 _null_ _null_ ));
  DESCR("text representation for text search");
  #define TSVECTOROID		3614
! DATA(insert OID = 3642 ( gtsvector		PGNSP PGUID -1 f b U f t \054 0 0 3644 gtsvectorin gtsvectorout - - - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("GiST index internal text representation for text search");
  #define GTSVECTOROID	3642
! DATA(insert OID = 3615 ( tsquery		PGNSP PGUID -1 f b U f t \054 0 0 3645 tsqueryin tsqueryout tsqueryrecv tsquerysend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("query representation for text search");
  #define TSQUERYOID		3615
! DATA(insert OID = 3734 ( regconfig		PGNSP PGUID 4 t b N f t \054 0 0 3735 regconfigin regconfigout regconfigrecv regconfigsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered text search configuration");
  #define REGCONFIGOID	3734
! DATA(insert OID = 3769 ( regdictionary	PGNSP PGUID 4 t b N f t \054 0 0 3770 regdictionaryin regdictionaryout regdictionaryrecv regdictionarysend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered text search dictionary");
  #define REGDICTIONARYOID	3769
  
! DATA(insert OID = 3643 ( _tsvector		PGNSP PGUID -1 f b A f t \054 0 3614 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 3644 ( _gtsvector		PGNSP PGUID -1 f b A f t \054 0 3642 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 3645 ( _tsquery		PGNSP PGUID -1 f b A f t \054 0 3615 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 3735 ( _regconfig		PGNSP PGUID -1 f b A f t \054 0 3734 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 3770 ( _regdictionary PGNSP PGUID -1 f b A f t \054 0 3769 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  
! DATA(insert OID = 2970 ( txid_snapshot	PGNSP PGUID -1 f b U f t \054 0 0 2949 txid_snapshot_in txid_snapshot_out txid_snapshot_recv txid_snapshot_send - - - d x f 0 -1 0 _null_ _null_ ));
  DESCR("txid snapshot");
! DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
  
  /*
   * pseudo-types
--- 369,599 ----
  /* OIDS 500 - 599 */
  
  /* OIDS 600 - 699 */
! DATA(insert OID = 600 (  point	   PGNSP PGUID 16 f b G f t \054 0 701 1017 point_in point_out point_recv point_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric point '(x, y)'");
  #define POINTOID		600
! DATA(insert OID = 601 (  lseg	   PGNSP PGUID 32 f b G f t \054 0 600 1018 lseg_in lseg_out lseg_recv lseg_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric line segment '(pt1,pt2)'");
  #define LSEGOID			601
! DATA(insert OID = 602 (  path	   PGNSP PGUID -1 f b G f t \054 0 0 1019 path_in path_out path_recv path_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric path '(pt1,...)'");
  #define PATHOID			602
! DATA(insert OID = 603 (  box	   PGNSP PGUID 32 f b G f t \073 0 600 1020 box_in box_out box_recv box_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric box '(lower left,upper right)'");
  #define BOXOID			603
! DATA(insert OID = 604 (  polygon   PGNSP PGUID -1 f b G f t \054 0	 0 1027 poly_in poly_out poly_recv poly_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric polygon '(pt1,...)'");
  #define POLYGONOID		604
  
! DATA(insert OID = 628 (  line	   PGNSP PGUID 32 f b G f t \054 0 701 629 line_in line_out line_recv line_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric line (not implemented)");
  #define LINEOID			628
! DATA(insert OID = 629 (  _line	   PGNSP PGUID	-1 f b A f t \054 0 628 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("");
  
  /* OIDS 700 - 799 */
  
! DATA(insert OID = 700 (  float4    PGNSP PGUID	4 FLOAT4PASSBYVAL b N f t \054 0	 0 1021 float4in float4out float4recv float4send - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("single-precision floating point number, 4-byte storage");
  #define FLOAT4OID 700
! DATA(insert OID = 701 (  float8    PGNSP PGUID	8 FLOAT8PASSBYVAL b N t t \054 0	 0 1022 float8in float8out float8recv float8send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("double-precision floating point number, 8-byte storage");
  #define FLOAT8OID 701
! DATA(insert OID = 702 (  abstime   PGNSP PGUID	4 t b D f t \054 0	 0 1023 abstimein abstimeout abstimerecv abstimesend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("absolute, limited-range date and time (Unix system time)");
  #define ABSTIMEOID		702
! DATA(insert OID = 703 (  reltime   PGNSP PGUID	4 t b T f t \054 0	 0 1024 reltimein reltimeout reltimerecv reltimesend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("relative, limited-range time interval (Unix delta time)");
  #define RELTIMEOID		703
! DATA(insert OID = 704 (  tinterval PGNSP PGUID 12 f b T f t \054 0	 0 1025 tintervalin tintervalout tintervalrecv tintervalsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("(abstime,abstime), time interval");
  #define TINTERVALOID	704
! DATA(insert OID = 705 (  unknown   PGNSP PGUID -2 f b X f t \054 0	 0 0 unknownin unknownout unknownrecv unknownsend - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("");
  #define UNKNOWNOID		705
  
! DATA(insert OID = 718 (  circle    PGNSP PGUID	24 f b G f t \054 0 0 719 circle_in circle_out circle_recv circle_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric circle '(center,radius)'");
  #define CIRCLEOID		718
! DATA(insert OID = 719 (  _circle   PGNSP PGUID	-1 f b A f t \054 0  718 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 790 (  money	   PGNSP PGUID	 8 FLOAT8PASSBYVAL b N f t \054 0 0 791 cash_in cash_out cash_recv cash_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("monetary amounts, $d,ddd.cc");
  #define CASHOID 790
! DATA(insert OID = 791 (  _money    PGNSP PGUID	-1 f b A f t \054 0  790 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 800 - 899 */
! DATA(insert OID = 829 ( macaddr    PGNSP PGUID	6 f b U f t \054 0 0 1040 macaddr_in macaddr_out macaddr_recv macaddr_send - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("XX:XX:XX:XX:XX:XX, MAC address");
  #define MACADDROID 829
! DATA(insert OID = 869 ( inet	   PGNSP PGUID	-1 f b I t t \054 0 0 1041 inet_in inet_out inet_recv inet_send - - - i m f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("IP address/netmask, host address, netmask optional");
  #define INETOID 869
! DATA(insert OID = 650 ( cidr	   PGNSP PGUID	-1 f b I f t \054 0 0 651 cidr_in cidr_out cidr_recv cidr_send - - - i m f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("network IP address/netmask, network address");
  #define CIDROID 650
  
  /* OIDS 900 - 999 */
  
  /* OIDS 1000 - 1099 */
! DATA(insert OID = 1000 (  _bool		 PGNSP PGUID -1 f b A f t \054 0	16 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1001 (  _bytea	 PGNSP PGUID -1 f b A f t \054 0	17 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1002 (  _char		 PGNSP PGUID -1 f b A f t \054 0	18 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1003 (  _name		 PGNSP PGUID -1 f b A f t \054 0	19 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1005 (  _int2		 PGNSP PGUID -1 f b A f t \054 0	21 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1006 (  _int2vector PGNSP PGUID -1 f b A f t \054 0	22 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1007 (  _int4		 PGNSP PGUID -1 f b A f t \054 0	23 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  #define INT4ARRAYOID		1007
! DATA(insert OID = 1008 (  _regproc	 PGNSP PGUID -1 f b A f t \054 0	24 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1009 (  _text		 PGNSP PGUID -1 f b A f t \054 0	25 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  #define TEXTARRAYOID		1009
! DATA(insert OID = 1028 (  _oid		 PGNSP PGUID -1 f b A f t \054 0	26 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1010 (  _tid		 PGNSP PGUID -1 f b A f t \054 0	27 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1011 (  _xid		 PGNSP PGUID -1 f b A f t \054 0	28 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1012 (  _cid		 PGNSP PGUID -1 f b A f t \054 0	29 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1013 (  _oidvector PGNSP PGUID -1 f b A f t \054 0	30 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1014 (  _bpchar	 PGNSP PGUID -1 f b A f t \054 0 1042 0 array_in array_out array_recv array_send bpchartypmodin bpchartypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1015 (  _varchar	 PGNSP PGUID -1 f b A f t \054 0 1043 0 array_in array_out array_recv array_send varchartypmodin varchartypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1016 (  _int8		 PGNSP PGUID -1 f b A f t \054 0	20 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1017 (  _point	 PGNSP PGUID -1 f b A f t \054 0 600 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1018 (  _lseg		 PGNSP PGUID -1 f b A f t \054 0 601 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1019 (  _path		 PGNSP PGUID -1 f b A f t \054 0 602 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1020 (  _box		 PGNSP PGUID -1 f b A f t \073 0 603 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1021 (  _float4	 PGNSP PGUID -1 f b A f t \054 0 700 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  #define FLOAT4ARRAYOID 1021
! DATA(insert OID = 1022 (  _float8	 PGNSP PGUID -1 f b A f t \054 0 701 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1023 (  _abstime	 PGNSP PGUID -1 f b A f t \054 0 702 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1024 (  _reltime	 PGNSP PGUID -1 f b A f t \054 0 703 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1025 (  _tinterval PGNSP PGUID -1 f b A f t \054 0 704 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1027 (  _polygon	 PGNSP PGUID -1 f b A f t \054 0 604 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1033 (  aclitem	 PGNSP PGUID 12 f b U f t \054 0 0 1034 aclitemin aclitemout - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("access control list");
  #define ACLITEMOID		1033
! DATA(insert OID = 1034 (  _aclitem	 PGNSP PGUID -1 f b A f t \054 0 1033 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1040 (  _macaddr	 PGNSP PGUID -1 f b A f t \054 0  829 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1041 (  _inet		 PGNSP PGUID -1 f b A f t \054 0  869 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 651  (  _cidr		 PGNSP PGUID -1 f b A f t \054 0  650 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1263 (  _cstring	 PGNSP PGUID -1 f b A f t \054 0 2275 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  #define CSTRINGARRAYOID		1263
  
! DATA(insert OID = 1042 ( bpchar		 PGNSP PGUID -1 f b S f t \054 0	0 1014 bpcharin bpcharout bpcharrecv bpcharsend bpchartypmodin bpchartypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("char(length), blank-padded string, fixed storage length");
  #define BPCHAROID		1042
! DATA(insert OID = 1043 ( varchar	 PGNSP PGUID -1 f b S f t \054 0	0 1015 varcharin varcharout varcharrecv varcharsend varchartypmodin varchartypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("varchar(length), non-blank-padded string, variable storage length");
  #define VARCHAROID		1043
  
! DATA(insert OID = 1082 ( date		 PGNSP PGUID	4 t b D f t \054 0	0 1182 date_in date_out date_recv date_send - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("date");
  #define DATEOID			1082
! DATA(insert OID = 1083 ( time		 PGNSP PGUID	8 FLOAT8PASSBYVAL b D f t \054 0	0 1183 time_in time_out time_recv time_send timetypmodin timetypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("time of day");
  #define TIMEOID			1083
  
  /* OIDS 1100 - 1199 */
! DATA(insert OID = 1114 ( timestamp	 PGNSP PGUID	8 FLOAT8PASSBYVAL b D f t \054 0	0 1115 timestamp_in timestamp_out timestamp_recv timestamp_send timestamptypmodin timestamptypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("date and time");
  #define TIMESTAMPOID	1114
! DATA(insert OID = 1115 ( _timestamp  PGNSP PGUID	-1 f b A f t \054 0 1114 0 array_in array_out array_recv array_send timestamptypmodin timestamptypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1182 ( _date		 PGNSP PGUID	-1 f b A f t \054 0 1082 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1183 ( _time		 PGNSP PGUID	-1 f b A f t \054 0 1083 0 array_in array_out array_recv array_send timetypmodin timetypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1184 ( timestamptz PGNSP PGUID	8 FLOAT8PASSBYVAL b D t t \054 0	0 1185 timestamptz_in timestamptz_out timestamptz_recv timestamptz_send timestamptztypmodin timestamptztypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("date and time with time zone");
  #define TIMESTAMPTZOID	1184
! DATA(insert OID = 1185 ( _timestamptz PGNSP PGUID -1 f b A f t \054 0	1184 0 array_in array_out array_recv array_send timestamptztypmodin timestamptztypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1186 ( interval	 PGNSP PGUID 16 f b T t t \054 0	0 1187 interval_in interval_out interval_recv interval_send intervaltypmodin intervaltypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("@ <number> <units>, time interval");
  #define INTERVALOID		1186
! DATA(insert OID = 1187 ( _interval	 PGNSP PGUID	-1 f b A f t \054 0 1186 0 array_in array_out array_recv array_send intervaltypmodin intervaltypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 1200 - 1299 */
! DATA(insert OID = 1231 (  _numeric	 PGNSP PGUID -1 f b A f t \054 0	1700 0 array_in array_out array_recv array_send numerictypmodin numerictypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1266 ( timetz		 PGNSP PGUID 12 f b D f t \054 0	0 1270 timetz_in timetz_out timetz_recv timetz_send timetztypmodin timetztypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("time of day with time zone");
  #define TIMETZOID		1266
! DATA(insert OID = 1270 ( _timetz	 PGNSP PGUID -1 f b A f t \054 0	1266 0 array_in array_out array_recv array_send timetztypmodin timetztypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 1500 - 1599 */
! DATA(insert OID = 1560 ( bit		 PGNSP PGUID -1 f b V f t \054 0	0 1561 bit_in bit_out bit_recv bit_send bittypmodin bittypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("fixed-length bit string");
  #define BITOID	 1560
! DATA(insert OID = 1561 ( _bit		 PGNSP PGUID -1 f b A f t \054 0	1560 0 array_in array_out array_recv array_send bittypmodin bittypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1562 ( varbit		 PGNSP PGUID -1 f b V t t \054 0	0 1563 varbit_in varbit_out varbit_recv varbit_send varbittypmodin varbittypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("variable-length bit string");
  #define VARBITOID	  1562
! DATA(insert OID = 1563 ( _varbit	 PGNSP PGUID -1 f b A f t \054 0	1562 0 array_in array_out array_recv array_send varbittypmodin varbittypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 1600 - 1699 */
  
  /* OIDS 1700 - 1799 */
! DATA(insert OID = 1700 ( numeric	   PGNSP PGUID -1 f b N f t \054 0	0 1231 numeric_in numeric_out numeric_recv numeric_send numerictypmodin numerictypmodout - i m f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("numeric(precision, decimal), arbitrary precision number");
  #define NUMERICOID		1700
  
! DATA(insert OID = 1790 ( refcursor	   PGNSP PGUID -1 f b U f t \054 0	0 2201 textin textout textrecv textsend - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("reference to cursor (portal name)");
  #define REFCURSOROID	1790
  
  /* OIDS 2200 - 2299 */
! DATA(insert OID = 2201 ( _refcursor    PGNSP PGUID -1 f b A f t \054 0 1790 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  
! DATA(insert OID = 2202 ( regprocedure  PGNSP PGUID	4 t b N f t \054 0	 0 2207 regprocedurein regprocedureout regprocedurerecv regproceduresend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered procedure (with args)");
  #define REGPROCEDUREOID 2202
  
! DATA(insert OID = 2203 ( regoper	   PGNSP PGUID	4 t b N f t \054 0	 0 2208 regoperin regoperout regoperrecv regopersend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered operator");
  #define REGOPEROID		2203
  
! DATA(insert OID = 2204 ( regoperator   PGNSP PGUID	4 t b N f t \054 0	 0 2209 regoperatorin regoperatorout regoperatorrecv regoperatorsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered operator (with args)");
  #define REGOPERATOROID	2204
  
! DATA(insert OID = 2205 ( regclass	   PGNSP PGUID	4 t b N f t \054 0	 0 2210 regclassin regclassout regclassrecv regclasssend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered class");
  #define REGCLASSOID		2205
  
! DATA(insert OID = 2206 ( regtype	   PGNSP PGUID	4 t b N f t \054 0	 0 2211 regtypein regtypeout regtyperecv regtypesend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered type");
  #define REGTYPEOID		2206
  
! DATA(insert OID = 2207 ( _regprocedure PGNSP PGUID -1 f b A f t \054 0 2202 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 2208 ( _regoper	   PGNSP PGUID -1 f b A f t \054 0 2203 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 2209 ( _regoperator  PGNSP PGUID -1 f b A f t \054 0 2204 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 2210 ( _regclass	   PGNSP PGUID -1 f b A f t \054 0 2205 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 2211 ( _regtype	   PGNSP PGUID -1 f b A f t \054 0 2206 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  #define REGTYPEARRAYOID 2211
  
  /* uuid */
! DATA(insert OID = 2950 ( uuid			PGNSP PGUID 16 f b U f t \054 0 0 2951 uuid_in uuid_out uuid_recv uuid_send - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("UUID datatype");
! DATA(insert OID = 2951 ( _uuid			PGNSP PGUID -1 f b A f t \054 0 2950 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* text search */
! DATA(insert OID = 3614 ( tsvector		PGNSP PGUID -1 f b U f t \054 0 0 3643 tsvectorin tsvectorout tsvectorrecv tsvectorsend - - ts_typanalyze i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("text representation for text search");
  #define TSVECTOROID		3614
! DATA(insert OID = 3642 ( gtsvector		PGNSP PGUID -1 f b U f t \054 0 0 3644 gtsvectorin gtsvectorout - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("GiST index internal text representation for text search");
  #define GTSVECTOROID	3642
! DATA(insert OID = 3615 ( tsquery		PGNSP PGUID -1 f b U f t \054 0 0 3645 tsqueryin tsqueryout tsqueryrecv tsquerysend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("query representation for text search");
  #define TSQUERYOID		3615
! DATA(insert OID = 3734 ( regconfig		PGNSP PGUID 4 t b N f t \054 0 0 3735 regconfigin regconfigout regconfigrecv regconfigsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered text search configuration");
  #define REGCONFIGOID	3734
! DATA(insert OID = 3769 ( regdictionary	PGNSP PGUID 4 t b N f t \054 0 0 3770 regdictionaryin regdictionaryout regdictionaryrecv regdictionarysend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered text search dictionary");
  #define REGDICTIONARYOID	3769
  
! DATA(insert OID = 3643 ( _tsvector		PGNSP PGUID -1 f b A f t \054 0 3614 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 3644 ( _gtsvector		PGNSP PGUID -1 f b A f t \054 0 3642 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 3645 ( _tsquery		PGNSP PGUID -1 f b A f t \054 0 3615 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 3735 ( _regconfig		PGNSP PGUID -1 f b A f t \054 0 3734 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 3770 ( _regdictionary PGNSP PGUID -1 f b A f t \054 0 3769 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  
! DATA(insert OID = 2970 ( txid_snapshot	PGNSP PGUID -1 f b U f t \054 0 0 2949 txid_snapshot_in txid_snapshot_out txid_snapshot_recv txid_snapshot_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("txid snapshot");
! DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  
  /*
   * pseudo-types
***************
*** 594,624 **** DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 a
   * but there is now support for it in records and arrays.  Perhaps we should
   * just treat it as a regular base type?
   */
! DATA(insert OID = 2249 ( record			PGNSP PGUID -1 f p P f t \054 0 0 2287 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
  #define RECORDOID		2249
! DATA(insert OID = 2287 ( _record		PGNSP PGUID -1 f p P f t \054 0 2249 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
  #define RECORDARRAYOID	2287
! DATA(insert OID = 2275 ( cstring		PGNSP PGUID -2 f p P f t \054 0 0 1263 cstring_in cstring_out cstring_recv cstring_send - - - c p f 0 -1 0 _null_ _null_ ));
  #define CSTRINGOID		2275
! DATA(insert OID = 2276 ( any			PGNSP PGUID  4 t p P f t \054 0 0 0 any_in any_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define ANYOID			2276
! DATA(insert OID = 2277 ( anyarray		PGNSP PGUID -1 f p P f t \054 0 0 0 anyarray_in anyarray_out anyarray_recv anyarray_send - - - d x f 0 -1 0 _null_ _null_ ));
  #define ANYARRAYOID		2277
! DATA(insert OID = 2278 ( void			PGNSP PGUID  4 t p P f t \054 0 0 0 void_in void_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define VOIDOID			2278
! DATA(insert OID = 2279 ( trigger		PGNSP PGUID  4 t p P f t \054 0 0 0 trigger_in trigger_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define TRIGGEROID		2279
! DATA(insert OID = 2280 ( language_handler	PGNSP PGUID  4 t p P f t \054 0 0 0 language_handler_in language_handler_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define LANGUAGE_HANDLEROID		2280
! DATA(insert OID = 2281 ( internal		PGNSP PGUID  SIZEOF_POINTER t p P f t \054 0 0 0 internal_in internal_out - - - - - ALIGNOF_POINTER p f 0 -1 0 _null_ _null_ ));
  #define INTERNALOID		2281
! DATA(insert OID = 2282 ( opaque			PGNSP PGUID  4 t p P f t \054 0 0 0 opaque_in opaque_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define OPAQUEOID		2282
! DATA(insert OID = 2283 ( anyelement		PGNSP PGUID  4 t p P f t \054 0 0 0 anyelement_in anyelement_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define ANYELEMENTOID	2283
! DATA(insert OID = 2776 ( anynonarray	PGNSP PGUID  4 t p P f t \054 0 0 0 anynonarray_in anynonarray_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define ANYNONARRAYOID	2776
! DATA(insert OID = 3500 ( anyenum		PGNSP PGUID  4 t p P f t \054 0 0 0 anyenum_in anyenum_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define ANYENUMOID		3500
  
  
--- 608,638 ----
   * but there is now support for it in records and arrays.  Perhaps we should
   * just treat it as a regular base type?
   */
! DATA(insert OID = 2249 ( record			PGNSP PGUID -1 f p P f t \054 0 0 2287 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  #define RECORDOID		2249
! DATA(insert OID = 2287 ( _record		PGNSP PGUID -1 f p P f t \054 0 2249 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  #define RECORDARRAYOID	2287
! DATA(insert OID = 2275 ( cstring		PGNSP PGUID -2 f p P f t \054 0 0 1263 cstring_in cstring_out cstring_recv cstring_send - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  #define CSTRINGOID		2275
! DATA(insert OID = 2276 ( any			PGNSP PGUID  4 t p P f t \054 0 0 0 any_in any_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define ANYOID			2276
! DATA(insert OID = 2277 ( anyarray		PGNSP PGUID -1 f p P f t \054 0 0 0 anyarray_in anyarray_out anyarray_recv anyarray_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  #define ANYARRAYOID		2277
! DATA(insert OID = 2278 ( void			PGNSP PGUID  4 t p P f t \054 0 0 0 void_in void_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define VOIDOID			2278
! DATA(insert OID = 2279 ( trigger		PGNSP PGUID  4 t p P f t \054 0 0 0 trigger_in trigger_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define TRIGGEROID		2279
! DATA(insert OID = 2280 ( language_handler	PGNSP PGUID  4 t p P f t \054 0 0 0 language_handler_in language_handler_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define LANGUAGE_HANDLEROID		2280
! DATA(insert OID = 2281 ( internal		PGNSP PGUID  SIZEOF_POINTER t p P f t \054 0 0 0 internal_in internal_out - - - - - ALIGNOF_POINTER p f 0 -1 0 -1 f _null_ _null_ ));
  #define INTERNALOID		2281
! DATA(insert OID = 2282 ( opaque			PGNSP PGUID  4 t p P f t \054 0 0 0 opaque_in opaque_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define OPAQUEOID		2282
! DATA(insert OID = 2283 ( anyelement		PGNSP PGUID  4 t p P f t \054 0 0 0 anyelement_in anyelement_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define ANYELEMENTOID	2283
! DATA(insert OID = 2776 ( anynonarray	PGNSP PGUID  4 t p P f t \054 0 0 0 anynonarray_in anynonarray_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define ANYNONARRAYOID	2776
! DATA(insert OID = 3500 ( anyenum		PGNSP PGUID  4 t p P f t \054 0 0 0 anyenum_in anyenum_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define ANYENUMOID		3500
  
  
*** a/src/include/catalog/pg_type_fn.h
--- b/src/include/catalog/pg_type_fn.h
***************
*** 50,56 **** extern Oid TypeCreate(Oid newTypeOid,
  		   char storage,
  		   int32 typeMod,
  		   int32 typNDims,
! 		   bool typeNotNull);
  
  extern void GenerateTypeDependencies(Oid typeNamespace,
  						 Oid typeObjectId,
--- 50,58 ----
  		   char storage,
  		   int32 typeMod,
  		   int32 typNDims,
!     	   bool typeNotNull,
! 		   int32 typeNLabels,
! 		   bool typeSorted);
  
  extern void GenerateTypeDependencies(Oid typeNamespace,
  						 Oid typeObjectId,
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
***************
*** 346,351 **** typedef enum NodeTag
--- 346,352 ----
  	T_AlterUserMappingStmt,
  	T_DropUserMappingStmt,
  	T_AlterTableSpaceOptionsStmt,
+ 	T_AlterEnumStmt,
  
  	/*
  	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 2171,2176 **** typedef struct CreateEnumStmt
--- 2171,2190 ----
  
  
  /* ----------------------
+  *		Alter Type Statement, enum types
+  * ----------------------
+  */
+ typedef struct AlterEnumStmt
+ {
+ 	NodeTag		type;
+ 	List	   *typeName;		/* qualified name (list of Value strings) */
+ 	char	   *newVal;			/* new enum value */
+ 	char	   *newValNeighbour;/* neighbouring enum value */
+ 	bool	    newValIsAfter;	/* new enum value is after neighbour? */
+ } AlterEnumStmt;
+ 
+ 
+ /* ----------------------
   *		Create View Statement
   * ----------------------
   */
*** a/src/test/regress/expected/enum.out
--- b/src/test/regress/expected/enum.out
***************
*** 25,30 **** ERROR:  invalid input value for enum rainbow: "mauve"
--- 25,108 ----
  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 typnlabels, typsorted
+ FROM pg_type
+ WHERE oid = 'planets'::regtype;
+  typnlabels | typsorted 
+ ------------+-----------
+           4 | t
+ (1 row)
+ 
+ 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 typnlabels, typsorted
+ FROM pg_type
+ WHERE oid = 'planets'::regtype;
+  typnlabels | typsorted 
+ ------------+-----------
+           8 | f
+ (1 row)
+ 
+ SELECT enumlabel, enumsortorder 
+ FROM pg_enum 
+ WHERE enumtypid = 'planets'::regtype
+ ORDER by 2;
+  enumlabel | enumsortorder 
+ -----------+---------------
+  mercury   |             1
+  venus     |             2
+  earth     |             3
+  mars      |             4
+  jupiter   |             5
+  saturn    |             6
+  uranus    |             7
+  neptune   |             8
+ (8 rows)
+ 
+ select 'mars'::planets > 'mercury' as using_sortorder;
+  using_sortorder 
+ -----------------
+  t
+ (1 row)
+ 
+ -- 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 label.
+ DROP TYPE planets;
+ --
  -- Basic table creation, row selection
  --
  CREATE TABLE enumtest (col rainbow);
***************
*** 403,409 **** 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 
! -----------+-----------
  (0 rows)
  
--- 481,487 ----
  
  SELECT * FROM pg_enum WHERE NOT EXISTS
    (SELECT 1 FROM pg_type WHERE pg_type.oid = enumtypid);
!  enumtypid | enumlabel | enumsortorder 
! -----------+-----------+---------------
  (0 rows)
  
*** a/src/test/regress/sql/enum.sql
--- b/src/test/regress/sql/enum.sql
***************
*** 16,21 **** SELECT 'red'::rainbow;
--- 16,69 ----
  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 typnlabels, typsorted
+ FROM pg_type
+ WHERE oid = 'planets'::regtype;
+ 
+ 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 typnlabels, typsorted
+ FROM pg_type
+ WHERE oid = 'planets'::regtype;
+ 
+ SELECT enumlabel, enumsortorder 
+ FROM pg_enum 
+ WHERE enumtypid = 'planets'::regtype
+ ORDER by 2;
+ 
+ select 'mars'::planets > 'mercury' as using_sortorder;
+ 
+ -- errors for adding labels 
+ ALTER TYPE planets ADD 
+ 	  'plutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutopluto';
+ 
+ ALTER TYPE planets ADD 'pluto' AFTER 'zeus';
+ 
+ DROP TYPE planets;
+ --
  -- Basic table creation, row selection
  --
  CREATE TABLE enumtest (col rainbow);
#2Alvaro Herrera
alvherre@commandprompt.com
In reply to: Andrew Dunstan (#1)
Re: WIP: extensible enums

Excerpts from Andrew Dunstan's message of lun ago 23 05:35:09 -0400 2010:

To add a label at the end of the list, do:

ALTER TYPE myenum ADD 'newlabel';

To add a label somewhere else, do:

ALTER TYPE myenum ADD 'newlabel' BEFORE 'existinglabel';

or

ALTER TYPE myenum ADD 'newlabel' AFTER 'existinglabel';

What do you need AFTER for? Seems to me that BEFORE should be enough.
(You already have the unadorned syntax for adding an item after the last
one, which is the corner case that BEFORE alone doesn't cover).

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#3David E. Wheeler
david@kineticode.com
In reply to: Andrew Dunstan (#1)
Re: WIP: extensible enums

On Aug 23, 2010, at 2:35 AM, Andrew Dunstan wrote:

I'm not wedded to the syntax. Let the bikeshedding begin.

Seems pretty good to me as-is.

David

#4Andrew Dunstan
andrew@dunslane.net
In reply to: Alvaro Herrera (#2)
Re: WIP: extensible enums

On Mon, August 23, 2010 11:49 am, Alvaro Herrera wrote:

Excerpts from Andrew Dunstan's message of lun ago 23 05:35:09 -0400 2010:

To add a label at the end of the list, do:

ALTER TYPE myenum ADD 'newlabel';

To add a label somewhere else, do:

ALTER TYPE myenum ADD 'newlabel' BEFORE 'existinglabel';

or

ALTER TYPE myenum ADD 'newlabel' AFTER 'existinglabel';

What do you need AFTER for? Seems to me that BEFORE should be enough.
(You already have the unadorned syntax for adding an item after the last
one, which is the corner case that BEFORE alone doesn't cover).

You're right. Strictly speaking we don't need it. But it doesn't hurt much
to provide it for a degree of symmetry.

cheers

andrew

#5Andrew Dunstan
andrew@dunslane.net
In reply to: Alvaro Herrera (#2)
Re: WIP: extensible enums

On Mon, August 23, 2010 11:49 am, Alvaro Herrera wrote:

Excerpts from Andrew Dunstan's message of lun ago 23 05:35:09 -0400 2010:

To add a label at the end of the list, do:

ALTER TYPE myenum ADD 'newlabel';

To add a label somewhere else, do:

ALTER TYPE myenum ADD 'newlabel' BEFORE 'existinglabel';

or

ALTER TYPE myenum ADD 'newlabel' AFTER 'existinglabel';

What do you need AFTER for? Seems to me that BEFORE should be enough.
(You already have the unadorned syntax for adding an item after the last
one, which is the corner case that BEFORE alone doesn't cover).

You're right. Strictly speaking we don't need it. But it doesn't hurt much
to provide it for a degree of symmetry.

cheers

andrew

#6Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#4)
Re: WIP: extensible enums

"Andrew Dunstan" <andrew@dunslane.net> writes:

On Mon, August 23, 2010 11:49 am, Alvaro Herrera wrote:

What do you need AFTER for? Seems to me that BEFORE should be enough.
(You already have the unadorned syntax for adding an item after the last
one, which is the corner case that BEFORE alone doesn't cover).

You're right. Strictly speaking we don't need it. But it doesn't hurt much
to provide it for a degree of symmetry.

I'm with Alvaro: drop the AFTER variant. It provides more than one way
to do the same thing, which isn't that exciting, and it's also going to
make it harder to document the performance issues. Without that, you
can just say "ADD BEFORE will make the enum slower, but plain ADD won't"
(ignoring the issue of OID wraparound, which'll confuse matters in any
case).

regards, tom lane

#7Josh Berkus
josh@agliodbs.com
In reply to: Andrew Dunstan (#4)
Re: WIP: extensible enums

You're right. Strictly speaking we don't need it. But it doesn't hurt much
to provide it for a degree of symmetry.

Swami Josh predicts that if we don't add AFTER now, we'll be adding it
in 2 years when enough people complain about it.

--
-- Josh Berkus
PostgreSQL Experts Inc.
http://www.pgexperts.com

#8Thom Brown
thom@linux.com
In reply to: Andrew Dunstan (#1)
Re: WIP: extensible enums

On 23 August 2010 10:35, Andrew Dunstan <andrew@dunslane.net> wrote:

Attached is a WIP patch that allows enums to be extended with additional
labels arbitrarily. As previously discussed, it works by adding an explicit
sort order column to pg_enum. It keeps track of whether the labels are
correctly sorted by oid value, and if so uses that for comparison, so the
possible performance impact on existing uses, and on almost all cases where
a label is added at the end of the list, should be negligible.

Open items include

  * some additional error checking required
  * missing documentation
  * pg_upgrade considerations

To add a label at the end of the list, do:

 ALTER TYPE myenum ADD 'newlabel';

To add a label somewhere else, do:

 ALTER TYPE myenum ADD 'newlabel' BEFORE 'existinglabel';

or

 ALTER TYPE myenum ADD 'newlabel' AFTER 'existinglabel';

I'm not wedded to the syntax. Let the bikeshedding begin.

cheers

andrew

When you write the supporting doc changes, you might want to add a
note in to mention that you cannot remove a label once it has been
added.

Will the modified enums remain intact after a dump/restore?

--
Thom Brown
Registered Linux user: #516935

#9David Fetter
david@fetter.org
In reply to: Alvaro Herrera (#2)
Re: WIP: extensible enums

On Mon, Aug 23, 2010 at 11:49:41AM -0400, Alvaro Herrera wrote:

Excerpts from Andrew Dunstan's message of lun ago 23 05:35:09 -0400 2010:

To add a label at the end of the list, do:

ALTER TYPE myenum ADD 'newlabel';

To add a label somewhere else, do:

ALTER TYPE myenum ADD 'newlabel' BEFORE 'existinglabel';

or

ALTER TYPE myenum ADD 'newlabel' AFTER 'existinglabel';

What do you need AFTER for? Seems to me that BEFORE should be enough.
(You already have the unadorned syntax for adding an item after the last
one, which is the corner case that BEFORE alone doesn't cover).

Making things easier for the users is a good thing all by itself :)

+1 for including both BEFORE and AFTER. Would it be worth it to allow
something like FIRST and LAST?

ALTER TYPE myenum ADD 'newlabel' FIRST;

ALTER TYPE myenum ADD 'newlabel' LAST;

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#10David Fetter
david@fetter.org
In reply to: Tom Lane (#6)
Re: WIP: extensible enums

On Mon, Aug 23, 2010 at 01:54:40PM -0400, Tom Lane wrote:

"Andrew Dunstan" <andrew@dunslane.net> writes:

On Mon, August 23, 2010 11:49 am, Alvaro Herrera wrote:

What do you need AFTER for? Seems to me that BEFORE should be
enough. (You already have the unadorned syntax for adding an
item after the last one, which is the corner case that BEFORE
alone doesn't cover).

You're right. Strictly speaking we don't need it. But it doesn't
hurt much to provide it for a degree of symmetry.

I'm with Alvaro: drop the AFTER variant. It provides more than one
way to do the same thing, which isn't that exciting,

Not to you, maybe, but to users, it's really handy to have intuitive,
rather than strictly orthogonal, ways to do things.

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#11Tom Lane
tgl@sss.pgh.pa.us
In reply to: Josh Berkus (#7)
Re: WIP: extensible enums

Josh Berkus <josh@agliodbs.com> writes:

Swami Josh predicts that if we don't add AFTER now, we'll be adding it
in 2 years when enough people complain about it.

If it's not there, no one will ever miss it. You might as well argue
that there should be a way of creating a foreign key reference by
ALTER'ing the referenced table instead of the referencing table.
Sure, if the SQL committee was into symmetry, they might have provided
such a thing. But they didn't and no one misses it.

regards, tom lane

#12Joseph Adams
joeyadams3.14159@gmail.com
In reply to: Tom Lane (#6)
Re: WIP: extensible enums

On Mon, Aug 23, 2010 at 1:54 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

"Andrew Dunstan" <andrew@dunslane.net> writes:

On Mon, August 23, 2010 11:49 am, Alvaro Herrera wrote:

What do you need AFTER for?  Seems to me that BEFORE should be enough.
(You already have the unadorned syntax for adding an item after the last
one, which is the corner case that BEFORE alone doesn't cover).

You're right. Strictly speaking we don't need it. But it doesn't hurt much
to provide it for a degree of symmetry.

I'm with Alvaro: drop the AFTER variant.  It provides more than one way
to do the same thing, which isn't that exciting, and it's also going to
make it harder to document the performance issues.  Without that, you
can just say "ADD BEFORE will make the enum slower, but plain ADD won't"
(ignoring the issue of OID wraparound, which'll confuse matters in any
case).

But what if you want to insert an OID at the end? You can't do it if
all you've got is BEFORE:

CREATE TYPE colors AS ENUM ('red', 'green', 'blue');

If I want it to become ('red', 'green', 'blue', 'orange'), what am I to do?

#13Thom Brown
thom@linux.com
In reply to: Joseph Adams (#12)
Re: WIP: extensible enums

On 23 August 2010 19:25, Joseph Adams <joeyadams3.14159@gmail.com> wrote:

On Mon, Aug 23, 2010 at 1:54 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

"Andrew Dunstan" <andrew@dunslane.net> writes:

On Mon, August 23, 2010 11:49 am, Alvaro Herrera wrote:

What do you need AFTER for?  Seems to me that BEFORE should be enough.
(You already have the unadorned syntax for adding an item after the last
one, which is the corner case that BEFORE alone doesn't cover).

You're right. Strictly speaking we don't need it. But it doesn't hurt much
to provide it for a degree of symmetry.

I'm with Alvaro: drop the AFTER variant.  It provides more than one way
to do the same thing, which isn't that exciting, and it's also going to
make it harder to document the performance issues.  Without that, you
can just say "ADD BEFORE will make the enum slower, but plain ADD won't"
(ignoring the issue of OID wraparound, which'll confuse matters in any
case).

But what if you want to insert an OID at the end?  You can't do it if
all you've got is BEFORE:

CREATE TYPE colors AS ENUM ('red', 'green', 'blue');

If I want it to become ('red', 'green', 'blue', 'orange'), what am I to do?

ALTER TYPE colors ADD 'orange';

--
Thom Brown
Registered Linux user: #516935

#14Tom Lane
tgl@sss.pgh.pa.us
In reply to: Thom Brown (#13)
Re: WIP: extensible enums

Thom Brown <thom@linux.com> writes:

On 23 August 2010 19:25, Joseph Adams <joeyadams3.14159@gmail.com> wrote:

But what if you want to insert an OID at the end?

ALTER TYPE colors ADD 'orange';

Alternatively, if people are dead set on symmetry, what we should do
to simplify is drop *this* syntax, and just have the BEFORE and AFTER
variants.

regards, tom lane

#15Josh Berkus
josh@agliodbs.com
In reply to: Tom Lane (#11)
Re: WIP: extensible enums

If it's not there, no one will ever miss it. You might as well argue
that there should be a way of creating a foreign key reference by
ALTER'ing the referenced table instead of the referencing table.
Sure, if the SQL committee was into symmetry, they might have provided
such a thing. But they didn't and no one misses it.

That's a very different situation, since the relationship is not
symmetrical, and it would take far more than a single keyword. Analogy
fail.

And one of the reasons people don't miss it is because far too many
users don't use FKs in the first place. ;-( The only reason why users
wouldn't notice the absence of AFTER (or, more likely, try it and then
ask on IRC for error message diagnosis) is because they're not using the
feature. (In which case it doesn't matter how it operates)

Docs which say "Add new enums BEFORE the enum you want to add them to,
and if you need to add an enum at the end, then add it without the
BEFORE keyword" is unnecessarily confusing to users. Saying "Add new
enum values using the BEFORE or AFTER keyword before or after the
appropriate value" is vastly easier to understand.

I really don't see the value in making a command substantially less
intuitive in order to avoid a single keyword, unless it affects areas of
Postgres outside of this particular command.

--
-- Josh Berkus
PostgreSQL Experts Inc.
http://www.pgexperts.com

#16Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Tom Lane (#14)
Re: WIP: extensible enums

On 23/08/10 22:06, Tom Lane wrote:

Thom Brown<thom@linux.com> writes:

On 23 August 2010 19:25, Joseph Adams<joeyadams3.14159@gmail.com> wrote:

But what if you want to insert an OID at the end?

ALTER TYPE colors ADD 'orange';

Alternatively, if people are dead set on symmetry, what we should do
to simplify is drop *this* syntax, and just have the BEFORE and AFTER
variants.

Then you need to know the last existing value to add a new one to the
end. Seems awkward.

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

#17Tom Lane
tgl@sss.pgh.pa.us
In reply to: Josh Berkus (#15)
Re: WIP: extensible enums

Josh Berkus <josh@agliodbs.com> writes:

I really don't see the value in making a command substantially less
intuitive in order to avoid a single keyword, unless it affects areas of
Postgres outside of this particular command.

It's the three variants to do two things that I find unintuitive.

As I mentioned a minute ago, dropping the "abbreviated" syntax and
just having BEFORE and AFTER would be a good way of achieving
symmetry if you find that important.

regards, tom lane

#18Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#14)
Re: WIP: extensible enums

On Mon, Aug 23, 2010 at 3:06 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Thom Brown <thom@linux.com> writes:

On 23 August 2010 19:25, Joseph Adams <joeyadams3.14159@gmail.com> wrote:

But what if you want to insert an OID at the end?

ALTER TYPE colors ADD 'orange';

Alternatively, if people are dead set on symmetry, what we should do
to simplify is drop *this* syntax, and just have the BEFORE and AFTER
variants.

FWIW, I think Andrew's originally proposed syntax is fine and useful,
and we should just go with it.

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

#19Andrew Dunstan
andrew@dunslane.net
In reply to: Heikki Linnakangas (#16)
Re: WIP: extensible enums

On Mon, August 23, 2010 3:20 pm, Heikki Linnakangas wrote:

On 23/08/10 22:06, Tom Lane wrote:

Thom Brown<thom@linux.com> writes:

On 23 August 2010 19:25, Joseph Adams<joeyadams3.14159@gmail.com>
wrote:

But what if you want to insert an OID at the end?

ALTER TYPE colors ADD 'orange';

Alternatively, if people are dead set on symmetry, what we should do
to simplify is drop *this* syntax, and just have the BEFORE and AFTER
variants.

Then you need to know the last existing value to add a new one to the
end. Seems awkward.

I agree. This is a non-starter, I think. The most common case in my
experience is where the user doesn't care at all about the order, and just
wants to add a new label. We should make that as easy as possible,
especially since it's the most efficient.

cheers

andrew

#20David E. Wheeler
david@kineticode.com
In reply to: Tom Lane (#17)
Re: WIP: extensible enums

On Aug 23, 2010, at 12:20 PM, Tom Lane wrote:

Josh Berkus <josh@agliodbs.com> writes:

I really don't see the value in making a command substantially less
intuitive in order to avoid a single keyword, unless it affects areas of
Postgres outside of this particular command.

It's the three variants to do two things that I find unintuitive.

I strongly suspect that you are in the minority on this one.

Best,

David

#21Josh Berkus
josh@agliodbs.com
In reply to: Tom Lane (#17)
Re: WIP: extensible enums

On 8/23/10 12:20 PM, Tom Lane wrote:

Josh Berkus <josh@agliodbs.com> writes:

I really don't see the value in making a command substantially less
intuitive in order to avoid a single keyword, unless it affects areas of
Postgres outside of this particular command.

It's the three variants to do two things that I find unintuitive.

Actually, it's 3 different things:

1. BEFORE adds a value before the value cited.
2. AFTER adds a value after the value cited.
3. unqualified adds a value at the end.

The fact that AFTER allows you to add a value at the end is
circumstantial overlap. While executing an AFTER, you wouldn't *know*
that you were adding it to the end, necessarily.

The other reason to have AFTER is that, in scripts, the user may not
have the before value handy due to context (i.e. dynamically building an
enum).

Anyway, this'll still be useful with BEFORE only. I'm just convinced
that we'll end up adding AFTER in 9.2 or 9.3 after we get a bunch of
user complaints and questions. So why not add it now?

--
-- Josh Berkus
PostgreSQL Experts Inc.
http://www.pgexperts.com

#22Bruce Momjian
bruce@momjian.us
In reply to: Josh Berkus (#21)
Re: WIP: extensible enums

Josh Berkus wrote:

On 8/23/10 12:20 PM, Tom Lane wrote:

Josh Berkus <josh@agliodbs.com> writes:

I really don't see the value in making a command substantially less
intuitive in order to avoid a single keyword, unless it affects areas of
Postgres outside of this particular command.

It's the three variants to do two things that I find unintuitive.

Actually, it's 3 different things:

1. BEFORE adds a value before the value cited.
2. AFTER adds a value after the value cited.
3. unqualified adds a value at the end.

The fact that AFTER allows you to add a value at the end is
circumstantial overlap. While executing an AFTER, you wouldn't *know*
that you were adding it to the end, necessarily.

The other reason to have AFTER is that, in scripts, the user may not
have the before value handy due to context (i.e. dynamically building an
enum).

Anyway, this'll still be useful with BEFORE only. I'm just convinced
that we'll end up adding AFTER in 9.2 or 9.3 after we get a bunch of
user complaints and questions. So why not add it now?

CREATE ENUM in PG 9.0 allows you to create an enum with no columns,
e.g.:

test=> CREATE TYPE etest AS ENUM ();
CREATE TYPE

so I think we have to have the ability add an enum without a
before/after. This ability was added for pg_upgrade.

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

+ It's impossible for everything to be true. +

#23Bruce Momjian
bruce@momjian.us
In reply to: Andrew Dunstan (#1)
Re: WIP: extensible enums

Andrew Dunstan wrote:

Attached is a WIP patch that allows enums to be extended with additional
labels arbitrarily. As previously discussed, it works by adding an
explicit sort order column to pg_enum. It keeps track of whether the
labels are correctly sorted by oid value, and if so uses that for
comparison, so the possible performance impact on existing uses, and on
almost all cases where a label is added at the end of the list, should
be negligible.

Open items include

* some additional error checking required
* missing documentation
* pg_upgrade considerations

I looked at the pg_upgrade ramifications of this change and it seems
some adjustments will have to be made. Right now pg_upgrade creates an
empty enum type:

CREATE TYPE etest AS ENUM ();

and then it calls EnumValuesCreate() to create the enum labels.
EnumValuesCreate() is called from within DefineEnum() where the enum
type is created, and that assumes the enums are always created initially
sorted. That would not be true when pg_upgrade is calling
EnumValuesCreate() directly with oid assignment as part of an upgrade.

I think the cleanest solution would be to modify pg_dump.c so that it
creates an empty ENUM type like before, but uses the new ALTER TYPE
myenum ADD 'newlabel' syntax to add the enum labels (with oid assignment
like we do for CREATE TYPE, etc.) The existing code had to hack to call
EnumValuesCreate() but with this new syntax it will no longer be
necessary. The call to EnumValuesCreate() for enums is the only time
pg_upgrade_support calls into a function rather than just assigning an
oid to a global variable, so it would be great to remove that last
direct function call usage.

Does this code handle the case where CREATE ENUM oid wraps around while
the enum label oids are being assigned? Does our existing code handle
that case?

I also noticed you grab an oid for the new type using the oid counter
without trying to make it in sorted order. Seems that would be possible
for adding enums to the end of the list, and might not be worth it. A
quick hack might be to just try of an oid+1 from the last enum and see
if that causes a conflict with the pg_enum.oid index.

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

+ It's impossible for everything to be true. +

#24Tom Lane
tgl@sss.pgh.pa.us
In reply to: David E. Wheeler (#20)
Re: WIP: extensible enums

"David E. Wheeler" <david@kineticode.com> writes:

On Aug 23, 2010, at 12:20 PM, Tom Lane wrote:

It's the three variants to do two things that I find unintuitive.

I strongly suspect that you are in the minority on this one.

Yeah, seems like I'm losing the argument. Oh well.

regards, tom lane

#25Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bruce Momjian (#23)
Re: WIP: extensible enums

Bruce Momjian <bruce@momjian.us> writes:

I also noticed you grab an oid for the new type using the oid counter
without trying to make it in sorted order. Seems that would be possible
for adding enums to the end of the list, and might not be worth it. A
quick hack might be to just try of an oid+1 from the last enum and see
if that causes a conflict with the pg_enum.oid index.

That wouldn't be race-condition-safe.

regards, tom lane

#26Andrew Dunstan
andrew@dunslane.net
In reply to: Bruce Momjian (#22)
Re: WIP: extensible enums

On 08/23/2010 07:12 PM, Bruce Momjian wrote:

Josh Berkus wrote:

On 8/23/10 12:20 PM, Tom Lane wrote:

Josh Berkus<josh@agliodbs.com> writes:

I really don't see the value in making a command substantially less
intuitive in order to avoid a single keyword, unless it affects areas of
Postgres outside of this particular command.

It's the three variants to do two things that I find unintuitive.

Actually, it's 3 different things:

1. BEFORE adds a value before the value cited.
2. AFTER adds a value after the value cited.
3. unqualified adds a value at the end.

The fact that AFTER allows you to add a value at the end is
circumstantial overlap. While executing an AFTER, you wouldn't *know*
that you were adding it to the end, necessarily.

The other reason to have AFTER is that, in scripts, the user may not
have the before value handy due to context (i.e. dynamically building an
enum).

Anyway, this'll still be useful with BEFORE only. I'm just convinced
that we'll end up adding AFTER in 9.2 or 9.3 after we get a bunch of
user complaints and questions. So why not add it now?

CREATE ENUM in PG 9.0 allows you to create an enum with no columns,
e.g.:

test=> CREATE TYPE etest AS ENUM ();
CREATE TYPE

so I think we have to have the ability add an enum without a
before/after. This ability was added for pg_upgrade.

No we don't. pg_upgrade calls a C function. There is no support for this
at the SQL level AIUI. And the ability to add labels at arbitrary
positions in the sort order is an essential part of this feature.

cheers

andrew

#27Bruce Momjian
bruce@momjian.us
In reply to: Andrew Dunstan (#26)
Re: WIP: extensible enums

Andrew Dunstan wrote:

On 08/23/2010 07:12 PM, Bruce Momjian wrote:

Josh Berkus wrote:

On 8/23/10 12:20 PM, Tom Lane wrote:

Josh Berkus<josh@agliodbs.com> writes:

I really don't see the value in making a command substantially less
intuitive in order to avoid a single keyword, unless it affects areas of
Postgres outside of this particular command.

It's the three variants to do two things that I find unintuitive.

Actually, it's 3 different things:

1. BEFORE adds a value before the value cited.
2. AFTER adds a value after the value cited.
3. unqualified adds a value at the end.

The fact that AFTER allows you to add a value at the end is
circumstantial overlap. While executing an AFTER, you wouldn't *know*
that you were adding it to the end, necessarily.

The other reason to have AFTER is that, in scripts, the user may not
have the before value handy due to context (i.e. dynamically building an
enum).

Anyway, this'll still be useful with BEFORE only. I'm just convinced
that we'll end up adding AFTER in 9.2 or 9.3 after we get a bunch of
user complaints and questions. So why not add it now?

CREATE ENUM in PG 9.0 allows you to create an enum with no columns,
e.g.:

test=> CREATE TYPE etest AS ENUM ();
CREATE TYPE

so I think we have to have the ability add an enum without a
before/after. This ability was added for pg_upgrade.

No we don't. pg_upgrade calls a C function. There is no support for this
at the SQL level AIUI. And the ability to add labels at arbitrary
positions in the sort order is an essential part of this feature.

pg_upgrade calls a C API to add labels, but the ability to create an
enum with no labels is supported at the SQL level, as I showed above. I
am not saying we don't need before/after, but I am saying we need the
ability to add labels without using before/after because there are no
labels in an empty enum.

I am not sure what you are arguing for/against. I thought we were
agreed to allow before/after, and no specification too. I am just
pointing out that we need the "no specification" syntax for logical as
well as practical reasons.

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

+ It's impossible for everything to be true. +

#28Andrew Dunstan
andrew@dunslane.net
In reply to: Bruce Momjian (#23)
Re: WIP: extensible enums

On 08/23/2010 07:34 PM, Bruce Momjian wrote:

I looked at the pg_upgrade ramifications of this change and it seems
some adjustments will have to be made. Right now pg_upgrade creates an
empty enum type:

CREATE TYPE etest AS ENUM ();

and then it calls EnumValuesCreate() to create the enum labels.
EnumValuesCreate() is called from within DefineEnum() where the enum
type is created, and that assumes the enums are always created initially
sorted. That would not be true when pg_upgrade is calling
EnumValuesCreate() directly with oid assignment as part of an upgrade.

I think the cleanest solution would be to modify pg_dump.c so that it
creates an empty ENUM type like before, but uses the new ALTER TYPE
myenum ADD 'newlabel' syntax to add the enum labels (with oid assignment
like we do for CREATE TYPE, etc.) The existing code had to hack to call
EnumValuesCreate() but with this new syntax it will no longer be
necessary. The call to EnumValuesCreate() for enums is the only time
pg_upgrade_support calls into a function rather than just assigning an
oid to a global variable, so it would be great to remove that last
direct function call usage.

I've just been taking another look at this suggestion. I think it will
work quite cleanly. As long as we add the enums in the correct order it
should just do the Right Thing (tm).

To answer your other question, Oid wraparound will not be a problem.

Will get coding.

cheers

andrew

#29Andrew Dunstan
andrew@dunslane.net
In reply to: Andrew Dunstan (#28)
1 attachment(s)
Re: WIP: extensible enums

On 08/25/2010 03:29 AM, Andrew Dunstan wrote:

I've just been taking another look at this suggestion. I think it will
work quite cleanly. As long as we add the enums in the correct order
it should just do the Right Thing (tm).

To answer your other question, Oid wraparound will not be a problem.

Will get coding.

Revised patch with pg_dump and pg_upgrade support is attached.

cheers

andrew

Attachments:

patchenum2text/plain; name=patchenum2Download
diff --git a/contrib/pg_upgrade/function.c b/contrib/pg_upgrade/function.c
index d9b54b8..9b3c64c 100644
--- a/contrib/pg_upgrade/function.c
+++ b/contrib/pg_upgrade/function.c
@@ -58,25 +58,25 @@ install_support_functions(migratorContext *ctx)
 								  "LANGUAGE C STRICT;"));
 		PQclear(executeQueryOrDie(ctx, conn,
 								  "CREATE OR REPLACE FUNCTION "
-				"		binary_upgrade.set_next_heap_relfilenode(OID) "
+					 "		binary_upgrade.set_next_pg_enum_oid(OID) "
 								  "RETURNS VOID "
 								  "AS '$libdir/pg_upgrade_support' "
 								  "LANGUAGE C STRICT;"));
 		PQclear(executeQueryOrDie(ctx, conn,
 								  "CREATE OR REPLACE FUNCTION "
-			   "		binary_upgrade.set_next_toast_relfilenode(OID) "
+				"		binary_upgrade.set_next_heap_relfilenode(OID) "
 								  "RETURNS VOID "
 								  "AS '$libdir/pg_upgrade_support' "
 								  "LANGUAGE C STRICT;"));
 		PQclear(executeQueryOrDie(ctx, conn,
 								  "CREATE OR REPLACE FUNCTION "
-			   "		binary_upgrade.set_next_index_relfilenode(OID) "
+			   "		binary_upgrade.set_next_toast_relfilenode(OID) "
 								  "RETURNS VOID "
 								  "AS '$libdir/pg_upgrade_support' "
 								  "LANGUAGE C STRICT;"));
 		PQclear(executeQueryOrDie(ctx, conn,
 								  "CREATE OR REPLACE FUNCTION "
-			 "		binary_upgrade.add_pg_enum_label(OID, OID, NAME) "
+			   "		binary_upgrade.set_next_index_relfilenode(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 50f9efd..7af7d4d 100644
--- a/contrib/pg_upgrade_support/pg_upgrade_support.c
+++ b/contrib/pg_upgrade_support/pg_upgrade_support.c
@@ -27,6 +27,7 @@ PG_MODULE_MAGIC;
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_array_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_toast_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_pg_enum_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;
@@ -34,18 +35,18 @@ extern PGDLLIMPORT Oid binary_upgrade_next_index_relfilenode;
 Datum set_next_pg_type_oid(PG_FUNCTION_ARGS);
 Datum set_next_pg_type_array_oid(PG_FUNCTION_ARGS);
 Datum set_next_pg_type_toast_oid(PG_FUNCTION_ARGS);
+Datum set_next_pg_enum_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);
 
 PG_FUNCTION_INFO_V1(set_next_pg_type_oid);
 PG_FUNCTION_INFO_V1(set_next_pg_type_array_oid);
 PG_FUNCTION_INFO_V1(set_next_pg_type_toast_oid);
+PG_FUNCTION_INFO_V1(set_next_pg_enum_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);
 
 Datum
 set_next_pg_type_oid(PG_FUNCTION_ARGS)
@@ -78,6 +79,16 @@ set_next_pg_type_toast_oid(PG_FUNCTION_ARGS)
 }
 
 Datum
+set_next_pg_enum_oid(PG_FUNCTION_ARGS)
+{
+	Oid			enumoid = PG_GETARG_OID(0);
+
+	binary_upgrade_next_pg_enum_oid = enumoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
 set_next_heap_relfilenode(PG_FUNCTION_ARGS)
 {
 	Oid			relfilenode = PG_GETARG_OID(0);
@@ -106,17 +117,3 @@ set_next_index_relfilenode(PG_FUNCTION_ARGS)
 
 	PG_RETURN_VOID();
 }
-
-Datum
-add_pg_enum_label(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);
-
-	PG_RETURN_VOID();
-}
-
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index a1d951a..3ef4e2d 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -854,7 +854,9 @@ AddNewRelationType(const char *typeName,
 				   'x',			/* fully TOASTable */
 				   -1,			/* typmod */
 				   0,			/* array dimensions for typBaseType */
-				   false);		/* Type NOT NULL */
+				   false,		/* Type NOT NULL */
+				   -1,          /* no enum labels */
+				   false);      /* type is not an enum, so not sorted */
 }
 
 /* --------------------------------
@@ -1086,7 +1088,9 @@ heap_create_with_catalog(const char *relname,
 				   'x',			/* fully TOASTable */
 				   -1,			/* typmod */
 				   0,			/* array dimensions for typBaseType */
-				   false);		/* Type NOT NULL */
+				   false,		/* Type NOT NULL */
+				   -1,          /* no enum labels */
+				   false);      /* type is not an enum, so not sorted */
 
 		pfree(relarrayname);
 	}
diff --git a/src/backend/catalog/pg_enum.c b/src/backend/catalog/pg_enum.c
index 578f8c5..d104d77 100644
--- a/src/backend/catalog/pg_enum.c
+++ b/src/backend/catalog/pg_enum.c
@@ -18,12 +18,202 @@
 #include "catalog/catalog.h"
 #include "catalog/indexing.h"
 #include "catalog/pg_enum.h"
+#include "catalog/pg_type.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/rel.h"
 #include "utils/tqual.h"
 
 static int	oid_cmp(const void *p1, const void *p2);
+static int	sort_order_cmp(const void *p1, const void *p2);
+
+Oid      binary_upgrade_next_pg_enum_oid = InvalidOid;
+
+/*
+ * 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.
+ *
+ * Returns true iff the labels are sorted by oid after the addition.
+ */
+
+bool
+AddEnumLabel(Oid enumTypeOid, 
+			 char *newVal, 
+			 char *neighbour, 
+			 bool newValIsAfter,
+			 int  nelems,
+			 bool elems_are_sorted)
+{
+	Oid        newOid;
+	Relation   pg_enum;
+	TupleDesc	tupDesc;
+	Datum		values[Natts_pg_enum];
+	bool		nulls[Natts_pg_enum];
+	NameData	enumlabel;
+	HeapTuple   enum_tup;
+	bool        result = elems_are_sorted;
+	int         newelemorder;
+
+	/* 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)));
+
+	/* get a new OID for the label */
+	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
+
+	if (OidIsValid(binary_upgrade_next_pg_enum_oid))
+	{
+		if (neighbour != NULL)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("BEFORE/AFTER incompatible with binary upgrade.")));
+
+		newOid = binary_upgrade_next_pg_enum_oid;
+		binary_upgrade_next_pg_enum_oid = InvalidOid;
+	}
+	else
+	{
+		/* non upgrade case */
+		newOid = GetNewOid(pg_enum);
+	}
+
+	if (neighbour == NULL)
+	{
+		/* 
+		 * Put the new label at the end of the list.
+		 * No change to existing tuples is required.
+		 */
+		newelemorder = nelems + 1;
+		/* are the elements still sorted? */
+		if (elems_are_sorted)
+		{	
+			CatCList   *list;
+			int			i;
+			bool        still_sorted = true;
+
+			list = SearchSysCacheList1(ENUMTYPOIDNAME, 
+									   ObjectIdGetDatum(enumTypeOid));
+			for (i = 0; i < nelems; i++)
+			{
+				HeapTuple tup = &(list->members[i]->tuple);
+				Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
+
+				if (en->enumsortorder == nelems)
+				{
+					if (HeapTupleGetOid(tup) > newOid)
+						still_sorted = false;
+					break;
+				}
+			}
+			ReleaseCatCacheList(list);
+			if (! still_sorted)
+				result = false;
+		}
+	}
+	else 
+	{
+		/* BEFORE or AFTER specified */
+		CatCList   *list;
+		int			i;
+		HeapTuple    *existing;
+		HeapTuple     nbr = NULL;
+		Form_pg_enum nbr_en;
+
+		/* get a list of the existing elements and sort them by enumsortorder */
+		list = SearchSysCacheList1(ENUMTYPOIDNAME, 
+								   ObjectIdGetDatum(enumTypeOid));
+		existing = palloc(nelems * sizeof(HeapTuple));
+
+		for (i = 0; i < nelems; i++)
+			existing[i] = &(list->members[i]->tuple);
+
+		qsort(existing, nelems, sizeof(HeapTuple), sort_order_cmp);
+		
+		/* locate the neighbour element */
+		for (i = 0; i < nelems; i++)
+		{
+			Form_pg_enum exists_en;
+			exists_en = (Form_pg_enum) GETSTRUCT(existing[i]);
+			if (strcmp(NameStr(exists_en->enumlabel),neighbour) == 0)
+				nbr = existing[i];
+
+		}
+
+		if (nbr == NULL)
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("\"%s\" is not an existing label.", neighbour)));
+		}
+
+		nbr_en = (Form_pg_enum) GETSTRUCT(nbr);
+
+		/* 
+		 * If BEFORE was specified, the new label goes in the neighbour's
+		 * position. Otherwise, it goes in the position after that.
+		 */
+		newelemorder = nbr_en->enumsortorder;
+		if (newValIsAfter)
+			newelemorder++;
+
+		/* 
+		 * Add 1 to the sortorder of all the labels after where the
+		 * new label goes. Do it from the end back so we don't get
+		 * uniqueness violations.
+		 */
+		for (i = nelems - 1; i>= 0; i--)
+		{
+			HeapTuple newtup;
+			Form_pg_enum exst_en = (Form_pg_enum) GETSTRUCT(existing[i]);
+			if (exst_en->enumsortorder < newelemorder)
+				break;
+			
+			newtup = heap_copytuple(existing[i]);
+			exst_en = (Form_pg_enum) GETSTRUCT(newtup);
+			exst_en->enumsortorder ++;
+
+			simple_heap_update(pg_enum, &newtup->t_self, newtup);
+
+			CatalogUpdateIndexes(pg_enum, newtup);
+
+		}
+
+		ReleaseCatCacheList(list);
+
+		/* are the labels sorted by OID? */
+		if (result && newelemorder > 1)
+			result = newOid > HeapTupleGetOid(existing[newelemorder-2]);
+		if (result && newelemorder < nelems + 1)
+			result = newOid < HeapTupleGetOid(existing[newelemorder-1]);
+ 
+	}
+
+	/* set up the new entry */
+	tupDesc = pg_enum->rd_att;
+	memset(nulls, false, sizeof(nulls));
+	values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
+	namestrcpy(&enumlabel, newVal);
+	values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
+	values[Anum_pg_enum_enumsortorder -1] =	Int32GetDatum(newelemorder);
+	enum_tup = heap_form_tuple(tupDesc, 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);
+
+	return result;
+
+}
 
 
 /*
@@ -33,8 +223,7 @@ 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;
@@ -50,9 +239,9 @@ 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);
@@ -62,35 +251,24 @@ EnumValuesCreate(Oid enumTypeOid, List *vals,
 	 * Allocate oids
 	 */
 	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
+
+	/*
+	 * 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.
+	 */
+	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.
+		 * The pg_enum.oid is stored in user tables.  This oid must be
+		 * preserved by binary upgrades.
 		 */
-		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);
+		oids[elemno] = GetNewOid(pg_enum);
 	}
+	/* sort them, just in case counter wrapped from high to low */
+	qsort(oids, num_elems, sizeof(Oid), oid_cmp);
 
 	/* and make the entries */
 	memset(nulls, false, sizeof(nulls));
@@ -114,6 +292,7 @@ EnumValuesCreate(Oid enumTypeOid, List *vals,
 		values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
 		namestrcpy(&enumlabel, lab);
 		values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
+		values[Anum_pg_enum_enumsortorder -1] = Int32GetDatum(elemno+1);
 
 		tup = heap_form_tuple(tupDesc, values, nulls);
 		HeapTupleSetOid(tup, oids[elemno]);
@@ -164,7 +343,7 @@ EnumValuesDelete(Oid enumTypeOid)
 }
 
 
-/* qsort comparison function */
+/* qsort comparison for oids */
 static int
 oid_cmp(const void *p1, const void *p2)
 {
@@ -177,3 +356,16 @@ 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);
+
+	return en1->enumsortorder - en2->enumsortorder;
+}
+
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index c7efd6d..109ee0e 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -112,6 +112,8 @@ TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId)
 	values[i++] = ObjectIdGetDatum(InvalidOid); /* typbasetype */
 	values[i++] = Int32GetDatum(-1);	/* typtypmod */
 	values[i++] = Int32GetDatum(0);		/* typndims */
+	values[i++] = Int32GetDatum(-1);	/* typnlabels */
+	values[i++] = BoolGetDatum(false);	/* typsorted */
 	nulls[i++] = true;			/* typdefaultbin */
 	nulls[i++] = true;			/* typdefault */
 
@@ -204,7 +206,9 @@ TypeCreate(Oid newTypeOid,
 		   char storage,
 		   int32 typeMod,
 		   int32 typNDims,		/* Array dimensions for baseType */
-		   bool typeNotNull)
+		   bool typeNotNull,
+		   int32 typeNLabels,
+		   bool typeSorted)
 {
 	Relation	pg_type_desc;
 	Oid			typeObjectId;
@@ -342,6 +346,8 @@ TypeCreate(Oid newTypeOid,
 	values[i++] = ObjectIdGetDatum(baseType);	/* typbasetype */
 	values[i++] = Int32GetDatum(typeMod);		/* typtypmod */
 	values[i++] = Int32GetDatum(typNDims);		/* typndims */
+	values[i++] = Int32GetDatum(typeNLabels);	/* typnlabels */
+	values[i++] = BoolGetDatum(typeSorted);	    /* typsorted */
 
 	/*
 	 * initialize the default binary value for this type.  Check for nulls of
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index fc80175..2ed8278 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -85,12 +85,11 @@ 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 checkEnumOwner(HeapTuple tup, TypeName *typename);
 static char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
 					Oid baseTypeOid,
 					int typMod, Constraint *constr,
 					char *domainName);
-
-
 /*
  * DefineType
  *		Registers a new base type.
@@ -562,8 +561,9 @@ DefineType(List *names, List *parameters)
 				   storage,		/* TOAST strategy */
 				   -1,			/* typMod (Domains only) */
 				   0,			/* Array Dimensions of typbasetype */
-				   false);		/* Type NOT NULL */
-
+				   false,		/* Type NOT NULL */
+				   -1,          /* no enum labels */
+				   false);        /* type is not an enum, so not sorted */
 	/*
 	 * Create the array type that goes with it.
 	 */
@@ -601,7 +601,9 @@ DefineType(List *names, List *parameters)
 			   'x',				/* ARRAY is always toastable */
 			   -1,				/* typMod (Domains only) */
 			   0,				/* Array dimensions of typbasetype */
-			   false);			/* Type NOT NULL */
+			   false,			/* Type NOT NULL */
+			   -1,              /* no enum labels */
+			   false);          /* type is not an enum, so not sorted */
 
 	pfree(array_type);
 }
@@ -1044,7 +1046,9 @@ DefineDomain(CreateDomainStmt *stmt)
 				   storage,		/* TOAST strategy */
 				   basetypeMod, /* typeMod value */
 				   typNDims,	/* Array dimensions for base type */
-				   typNotNull); /* Type NOT NULL */
+				   typNotNull,  /* Type NOT NULL */
+				   -1,          /* no enum labels */
+				   false);      /* type is not an enum, so not sorted */
 
 	/*
 	 * Process constraints which refer to the domain ID returned by TypeCreate
@@ -1094,6 +1098,9 @@ DefineEnum(CreateEnumStmt *stmt)
 	AclResult	aclresult;
 	Oid			old_type_oid;
 	Oid			enumArrayOid;
+	int         num_labels;
+
+	num_labels = list_length(stmt->vals);
 
 	/* Convert list of names to a name and namespace */
 	enumNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1153,10 +1160,12 @@ DefineEnum(CreateEnumStmt *stmt)
 				   'p',			/* TOAST strategy always plain */
 				   -1,			/* typMod (Domains only) */
 				   0,			/* Array dimensions of typbasetype */
-				   false);		/* Type NOT NULL */
+				   false,		/* Type NOT NULL */
+				   num_labels,  /* count enum labels */
+				   true);       /* enums always start sorted */
 
 	/* 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.
@@ -1192,11 +1201,88 @@ DefineEnum(CreateEnumStmt *stmt)
 			   'x',				/* ARRAY is always toastable */
 			   -1,				/* typMod (Domains only) */
 			   0,				/* Array dimensions of typbasetype */
-			   false);			/* Type NOT NULL */
+			   false,			/* Type NOT NULL */
+			   -1,              /* no enum labels */
+			   false);          /* type is not an enum, so not sorted */
 
 	pfree(enumArrayName);
 }
 
+/*
+ * AlterEnum
+ *		Registers a new label for an existing enum.
+ */
+void
+AlterEnum (AlterEnumStmt *stmt)
+{
+	Oid			enum_type_oid;
+	TypeName  *typename;
+	bool       sorted;
+	HeapTuple tup, newtup;
+	Relation  rel;
+	Form_pg_type typTup;
+
+	/* Make a TypeName so we can use standard type lookup machinery */
+	typename = makeTypeNameFromNameList(stmt->typeName);
+	enum_type_oid = typenameTypeId(NULL, typename, NULL);
+
+	/* Look up the row in the type table */
+	rel = heap_open(TypeRelationId, RowExclusiveLock);
+
+	tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(enum_type_oid));
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for type %u", enum_type_oid);
+
+	/* Copy the syscache entry so we can scribble on it below */
+	newtup = heap_copytuple(tup);
+	ReleaseSysCache(tup);
+	tup = newtup;
+	typTup = (Form_pg_type) GETSTRUCT(tup);
+
+	/* Check it's an enum and check user has permission to ALTER the enum */
+	checkEnumOwner(tup, typename);
+
+	/* Add the new label */
+	sorted = AddEnumLabel (enum_type_oid, stmt->newVal, 
+						   stmt->newValNeighbour, stmt->newValIsAfter,
+						   typTup->typnlabels, typTup->typsorted);
+
+	/* Update the row in pg_type */
+	typTup->typnlabels += 1;
+	typTup->typsorted = sorted;
+
+	simple_heap_update(rel, &tup->t_self, tup);
+
+	CatalogUpdateIndexes(rel, tup);
+
+	/* Clean up */
+	heap_close(rel, RowExclusiveLock);
+}
+
+
+/*
+ * 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, TypeName *typename)
+{
+	Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup);
+
+	/* Check that this is actually a domain */
+	if (typTup->typtype != TYPTYPE_ENUM)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is not an enum",
+						TypeNameToString(typename))));
+
+	/* 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.
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index b3e907a..59cfe96 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2836,6 +2836,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(newValNeighbour);
+	COPY_SCALAR_FIELD(newValIsAfter);
+
+	return newnode;
+}
+
 static ViewStmt *
 _copyViewStmt(ViewStmt *from)
 {
@@ -3989,6 +4002,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 7ec0923..900bf37 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1375,6 +1375,17 @@ _equalCreateEnumStmt(CreateEnumStmt *a, CreateEnumStmt *b)
 }
 
 static bool
+_equalAlterEnumStmt(AlterEnumStmt *a, AlterEnumStmt *b)
+{
+	COMPARE_NODE_FIELD(typeName);
+	COMPARE_STRING_FIELD(newVal);
+	COMPARE_STRING_FIELD(newValNeighbour);
+	COMPARE_SCALAR_FIELD(newValIsAfter);
+
+	return true;
+}
+
+static bool
 _equalViewStmt(ViewStmt *a, ViewStmt *b)
 {
 	COMPARE_NODE_FIELD(view);
@@ -2678,6 +2689,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 d5a0f29..9ef84aa 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -181,8 +181,8 @@ static TypeName *TableFuncTypeName(List *columns);
 }
 
 %type <node>	stmt schema_stmt
-		AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterFdwStmt
-		AlterForeignServerStmt AlterGroupStmt
+        AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
+        AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
 		AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
 		AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
 		AlterRoleStmt AlterRoleSetStmt
@@ -648,6 +648,7 @@ stmt :
 			| AlterDatabaseSetStmt
 			| AlterDefaultPrivilegesStmt
 			| AlterDomainStmt
+			| AlterEnumStmt
 			| AlterFdwStmt
 			| AlterForeignServerStmt
 			| AlterFunctionStmt
@@ -3781,6 +3782,46 @@ 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->newValNeighbour = 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->newValNeighbour = $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->newValNeighbour = $7;
+				 n->newValIsAfter = true;
+
+				 $$ = (Node *) n;
+			 }
+		 ;
+
+
 
 /*****************************************************************************
  *
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 77ee926..39ea03e 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -189,6 +189,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:
@@ -846,6 +847,10 @@ standard_ProcessUtility(Node *parsetree,
 			DefineEnum((CreateEnumStmt *) parsetree);
 			break;
 
+		case T_AlterEnumStmt:	/* ALTER TYPE (enum) */
+			AlterEnum((AlterEnumStmt *) parsetree);
+			break;
+
 		case T_ViewStmt:		/* CREATE VIEW */
 			DefineView((ViewStmt *) parsetree, queryString);
 			break;
@@ -1846,6 +1851,10 @@ CreateCommandTag(Node *parsetree)
 			tag = "CREATE TYPE";
 			break;
 
+		case T_AlterEnumStmt:
+			tag = "ALTER TYPE";
+			break;
+
 		case T_ViewStmt:
 			tag = "CREATE VIEW";
 			break;
@@ -2384,6 +2393,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 74cc0c2..63eab09 100644
--- a/src/backend/utils/adt/enum.c
+++ b/src/backend/utils/adt/enum.c
@@ -14,6 +14,7 @@
 #include "postgres.h"
 
 #include "catalog/pg_enum.h"
+#include "catalog/pg_type.h"
 #include "fmgr.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
@@ -22,9 +23,26 @@
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
 
+typedef struct 
+{
+	Oid      enum_oid;
+	int32   sort_order;
+} enum_sort;
+
+typedef struct 
+{
+	Oid       enumtypoid;
+	bool      oids_are_sorted;
+	int       sort_list_length;
+	int       label_count;
+	enum_sort sort_order_list[1];
+} enum_sort_cache;
+	
+
 
 static ArrayType *enum_range_internal(Oid enumtypoid, Oid lower, Oid upper);
-static int	enum_elem_cmp(const void *left, const void *right);
+static int	enum_sort_cmp(const void *left, const void *right);
+static int	enum_oid_cmp(const void *left, const void *right);
 
 
 /* Basic I/O support */
@@ -155,13 +173,142 @@ enum_send(PG_FUNCTION_ARGS)
 
 /* Comparison functions and related */
 
+static inline int 
+enum_ccmp(Oid arg1, Oid arg2, FunctionCallInfo fcinfo)
+{
+
+	enum_sort_cache * mycache;
+	enum_sort *es1, *es2, srch;
+	int sort1, sort2;
+	bool added = false;
+	HeapTuple	enum_tup, type_tup;
+	Form_pg_enum en;
+	Oid typeoid;
+	Form_pg_type typ;
+
+	if (arg1 == arg2)
+		return 0;
+
+	mycache = (enum_sort_cache *) fcinfo->flinfo->fn_extra;
+	if (mycache == NULL )
+	{
+		enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg1));
+		en = (Form_pg_enum) GETSTRUCT(enum_tup);
+		typeoid = en->enumtypid;
+		type_tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typeoid));
+		typ = (Form_pg_type)  GETSTRUCT(type_tup);
+		if (typ->typtype != 'e')
+			elog(ERROR,"wrong type for oid %u",typeoid);
+        fcinfo->flinfo->fn_extra = 
+		  MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+							 sizeof(enum_sort_cache) + 
+							 (typ->typnlabels * sizeof(enum_sort)));
+        mycache = (enum_sort_cache *) fcinfo->flinfo->fn_extra;
+		mycache->enumtypoid = typeoid;
+		mycache->sort_list_length = 1;
+		mycache->label_count = typ->typnlabels;
+		mycache->oids_are_sorted = typ->typsorted;
+		mycache->sort_order_list[0].enum_oid = arg1;
+		mycache->sort_order_list[0].sort_order = en->enumsortorder;
+		ReleaseSysCache(type_tup);
+		ReleaseSysCache(enum_tup);
+	}
+
+	if (mycache->oids_are_sorted)
+		return arg1 - arg2;
+
+	srch.enum_oid = arg1;
+	es1 = bsearch(&srch,
+				  mycache->sort_order_list,
+				  mycache->sort_list_length,
+				  sizeof(enum_sort),
+				  enum_oid_cmp);
+	srch.enum_oid = arg2;
+	es2 = bsearch(&srch,
+				  mycache->sort_order_list,
+				  mycache->sort_list_length,
+				  sizeof(enum_sort),
+				  enum_oid_cmp);
+
+	if (es1 == NULL)
+	{
+		
+		enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg1));
+		en = (Form_pg_enum) GETSTRUCT(enum_tup);
+		mycache->sort_order_list[mycache->sort_list_length].enum_oid = arg1;
+		sort1 = en->enumsortorder;
+		mycache->sort_order_list[mycache->sort_list_length].sort_order = 
+			sort1; 
+		ReleaseSysCache(enum_tup);
+		mycache->sort_list_length ++;
+		added = true;
+	}
+	else
+	{
+		/* already in cache */
+		sort1 = es1->sort_order;
+	}
+
+	if (es2 == NULL)
+	{
+		
+		enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg2));
+		en = (Form_pg_enum) GETSTRUCT(enum_tup);
+		sort2 = en->enumsortorder;
+		mycache->sort_order_list[mycache->sort_list_length].enum_oid = arg2;
+		mycache->sort_order_list[mycache->sort_list_length].sort_order = 
+			sort2;
+		ReleaseSysCache(enum_tup);
+		mycache->sort_list_length ++;
+		added = true;
+	}
+	else
+	{
+		/* already in cache */
+		sort2 = es2->sort_order;
+	}
+
+	if (added)
+	{
+		/* 
+		 * If we have more than a handful, just fetch them all, so we limit
+		 * the number of sort operations required.
+		 */
+		if (mycache->sort_list_length > 10 && 
+			mycache->sort_list_length < mycache->label_count)
+		{
+			CatCList   *nlist;
+			int			num, i;
+
+			nlist = SearchSysCacheList1(ENUMTYPOIDNAME, 
+									   ObjectIdGetDatum(mycache->enumtypoid));
+			num = nlist->n_members;
+			for (i = 0; i < num; i++)
+			{
+				HeapTuple tup = &(nlist->members[i]->tuple);
+				Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
+				
+				mycache->sort_order_list[i].enum_oid = HeapTupleGetOid(tup);
+				mycache->sort_order_list[i].sort_order = en->enumsortorder; 
+			}
+
+			ReleaseCatCacheList(nlist);
+		}
+
+		qsort(mycache->sort_order_list,mycache->sort_list_length,
+			  sizeof(enum_sort),enum_oid_cmp);
+	}
+
+	return sort1 - sort2;
+}
+
 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_ccmp(a,b,fcinfo) < 0);
 }
 
 Datum
@@ -170,7 +317,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_ccmp(a,b,fcinfo) <= 0);
 }
 
 Datum
@@ -197,7 +344,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_ccmp(a,b,fcinfo) >= 0);
 }
 
 Datum
@@ -206,7 +353,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_ccmp(a,b,fcinfo) > 0);
 }
 
 Datum
@@ -215,7 +362,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_ccmp(a,b,fcinfo) < 0 ? a : b);
 }
 
 Datum
@@ -224,7 +371,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_ccmp(a,b,fcinfo) > 0 ? a : b);
 }
 
 Datum
@@ -233,10 +380,10 @@ 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_ccmp(a,b,fcinfo) > 0)
+		PG_RETURN_INT32(1);
 	else
 		PG_RETURN_INT32(-1);
 }
@@ -248,6 +395,7 @@ enum_first(PG_FUNCTION_ARGS)
 {
 	Oid			enumtypoid;
 	Oid			min = InvalidOid;
+	int         min_sort = -1; /* value will never in fact be used */
 	CatCList   *list;
 	int			num,
 				i;
@@ -267,10 +415,14 @@ enum_first(PG_FUNCTION_ARGS)
 	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;
+		HeapTuple tup = &(list->members[i]->tuple);
+		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
+
+		if (!OidIsValid(min) || en->enumsortorder < min_sort)
+		{
+			min = HeapTupleGetOid(tup);
+			min_sort = en->enumsortorder;
+		}
 	}
 
 	ReleaseCatCacheList(list);
@@ -287,6 +439,7 @@ enum_last(PG_FUNCTION_ARGS)
 {
 	Oid			enumtypoid;
 	Oid			max = InvalidOid;
+	int         max_sort = -1; /* value will never in fact be used */
 	CatCList   *list;
 	int			num,
 				i;
@@ -306,10 +459,14 @@ enum_last(PG_FUNCTION_ARGS)
 	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;
+		HeapTuple tup = &(list->members[i]->tuple);
+		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
+
+		if (!OidIsValid(max) || en->enumsortorder > max_sort)
+		{
+			max = HeapTupleGetOid(tup);
+			max_sort = en->enumsortorder;
+		}
 	}
 
 	ReleaseCatCacheList(list);
@@ -382,46 +539,76 @@ enum_range_internal(Oid enumtypoid, Oid lower, Oid upper)
 				i,
 				j;
 	Datum	   *elems;
+	enum_sort  *sort_items;
+	bool        left_found;
 
 	list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid));
 	total = list->n_members;
 
 	elems = (Datum *) palloc(total * sizeof(Datum));
+	sort_items = (enum_sort *) palloc(total * sizeof(enum_sort));
 
-	j = 0;
 	for (i = 0; i < total; i++)
 	{
-		Oid			val = HeapTupleGetOid(&(list->members[i]->tuple));
+		HeapTuple tup = &(list->members[i]->tuple);
+		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
+
+		sort_items[i].enum_oid = HeapTupleGetOid(tup);
+		sort_items[i].sort_order = en->enumsortorder;
 
-		if ((!OidIsValid(lower) || lower <= val) &&
-			(!OidIsValid(upper) || val <= upper))
-			elems[j++] = ObjectIdGetDatum(val);
 	}
 
 	/* shouldn't need the cache anymore */
 	ReleaseCatCacheList(list);
 
-	/* sort results into OID order */
-	qsort(elems, j, sizeof(Datum), enum_elem_cmp);
+	/* sort results into sort_order sequence */
+	qsort(sort_items, total, sizeof(enum_sort), enum_sort_cmp);
+
+	j = 0; 
+	left_found = !OidIsValid(lower);
+	for (i=0; i < total; i++)
+	{
+		if (! left_found && lower == sort_items[i].enum_oid)
+			left_found = true;
+
+		if (left_found)
+			elems[j++] = ObjectIdGetDatum(sort_items[i].enum_oid);
+
+		if (OidIsValid(upper) && upper == sort_items[i].enum_oid)
+			break;
+	}
 
 	/* note this hardwires some details about the representation of Oid */
 	result = construct_array(elems, j, enumtypoid, sizeof(Oid), true, 'i');
 
 	pfree(elems);
+	pfree(sort_items);
 
 	return result;
 }
 
-/* qsort comparison function for Datums that are OIDs */
+/* 
+ * qsort comparison using sort order, for range routines 
+ */
 static int
-enum_elem_cmp(const void *left, const void *right)
+enum_sort_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;
+	enum_sort  *l = (enum_sort *) left;
+	enum_sort  *r = (enum_sort *) right;
+
+	return l->sort_order - r->sort_order;
 }
+
+/* 
+ * qsort comparison using OID order for comparison search cache
+ */
+static int 
+enum_oid_cmp(const void *es1, const void *es2)
+{
+	enum_sort *p1, *p2;
+	p1 = (enum_sort *)es1;
+	p2 = (enum_sort *)es2;
+	return p1->enum_oid - p2->enum_oid;
+}
+
+
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 01e1a48..94ae650 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6605,17 +6605,24 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 	PQExpBuffer query = createPQExpBuffer();
 	PGresult   *res;
 	int			num,
-				i;
+		        i;
 	Oid			enum_oid;
 	char	   *label;
 
 	/* Set proper schema search path so regproc references list correctly */
 	selectSourceSchema(tyinfo->dobj.namespace->dobj.name);
 
-	appendPQExpBuffer(query, "SELECT oid, enumlabel "
-					  "FROM pg_catalog.pg_enum "
-					  "WHERE enumtypid = '%u'"
-					  "ORDER BY oid",
+    if  (fout->remoteVersion > 90000)
+		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);
@@ -6661,17 +6668,19 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 		{
 			enum_oid = atooid(PQgetvalue(res, i, PQfnumber(res, "oid")));
 			label = PQgetvalue(res, i, PQfnumber(res, "enumlabel"));
-
+			
 			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);
-			appendStringLiteralAH(q, label, fout);
-			appendPQExpBuffer(q, ");\n");
+							  "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\n");
 		}
-		appendPQExpBuffer(q, "\n");
 	}
 
 	ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId,
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index e56e15f..8db3800 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, 3539, on pg_enum using btree(enumtypid oid_ops, enumsortorder int4_ops));
+#define EnumTypIdSortOrderIndexId 3539
 
 /* 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_class.h b/src/include/catalog/pg_class.h
index de34576..184cfb3 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -132,7 +132,7 @@ typedef FormData_pg_class *Form_pg_class;
  */
 
 /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f f r 28 0 t f f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f f r 30 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
 DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f f r 19 0 f f f f f f 3 _null_ _null_ ));
 DESCR("");
diff --git a/src/include/catalog/pg_enum.h b/src/include/catalog/pg_enum.h
index 3a1df77..b85f9cf 100644
--- a/src/include/catalog/pg_enum.h
+++ b/src/include/catalog/pg_enum.h
@@ -35,6 +35,7 @@ CATALOG(pg_enum,3501)
 {
 	Oid			enumtypid;		/* OID of owning enum type */
 	NameData	enumlabel;		/* text representation of enum value */
+	int4        enumsortorder;  /* sort order for this enum label */
 } 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		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 bool AddEnumLabel(Oid enumTypeOid, char *newVal, char *neighbour, 
+						 bool newValIsAfter, int nelems, bool elems_are_sorted);
 
 #endif   /* PG_ENUM_H */
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index b2e14ce..3ea2118 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -194,6 +194,18 @@ CATALOG(pg_type,1247) BKI_BOOTSTRAP BKI_ROWTYPE_OID(71) BKI_SCHEMA_MACRO
 	 */
 	int4		typndims;
 
+    /* 
+     * typnlabels, contains a count on the number of labels an enum type has, 
+     * -1 for a non-enum type.
+     */
+    int4          typnlabels;
+
+    /*
+     * typsorted is true if the oids of an enum type reflect the type's sort
+     * order, false otherwise including for a non-enum type.
+     */
+    bool         typsorted;
+
 	/*
 	 * If typdefaultbin is not NULL, it is the nodeToString representation of
 	 * a default expression for the type.  Currently this is only used for
@@ -224,7 +236,7 @@ typedef FormData_pg_type *Form_pg_type;
  *		compiler constants for pg_type
  * ----------------
  */
-#define Natts_pg_type					28
+#define Natts_pg_type					30
 #define Anum_pg_type_typname			1
 #define Anum_pg_type_typnamespace		2
 #define Anum_pg_type_typowner			3
@@ -251,8 +263,10 @@ typedef FormData_pg_type *Form_pg_type;
 #define Anum_pg_type_typbasetype		24
 #define Anum_pg_type_typtypmod			25
 #define Anum_pg_type_typndims			26
-#define Anum_pg_type_typdefaultbin		27
-#define Anum_pg_type_typdefault			28
+#define Anum_pg_type_typnlabels			27
+#define Anum_pg_type_typsorted			28
+#define Anum_pg_type_typdefaultbin		29
+#define Anum_pg_type_typdefault			30
 
 
 /* ----------------
@@ -269,83 +283,83 @@ typedef FormData_pg_type *Form_pg_type;
  */
 
 /* OIDS 1 - 99 */
-DATA(insert OID = 16 (	bool	   PGNSP PGUID	1 t b B t t \054 0	 0 1000 boolin boolout boolrecv boolsend - - - c p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 16 (	bool	   PGNSP PGUID	1 t b B t t \054 0	 0 1000 boolin boolout boolrecv boolsend - - - c p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("boolean, 'true'/'false'");
 #define BOOLOID			16
 
-DATA(insert OID = 17 (	bytea	   PGNSP PGUID -1 f b U f t \054 0	0 1001 byteain byteaout bytearecv byteasend - - - i x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 17 (	bytea	   PGNSP PGUID -1 f b U f t \054 0	0 1001 byteain byteaout bytearecv byteasend - - - i x f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("variable-length string, binary values escaped");
 #define BYTEAOID		17
 
-DATA(insert OID = 18 (	char	   PGNSP PGUID	1 t b S f t \054 0	 0 1002 charin charout charrecv charsend - - - c p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 18 (	char	   PGNSP PGUID	1 t b S f t \054 0	 0 1002 charin charout charrecv charsend - - - c p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("single character");
 #define CHAROID			18
 
-DATA(insert OID = 19 (	name	   PGNSP PGUID NAMEDATALEN f b S f t \054 0 18 1003 namein nameout namerecv namesend - - - c p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 19 (	name	   PGNSP PGUID NAMEDATALEN f b S f t \054 0 18 1003 namein nameout namerecv namesend - - - c p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("63-character type for storing system identifiers");
 #define NAMEOID			19
 
-DATA(insert OID = 20 (	int8	   PGNSP PGUID	8 FLOAT8PASSBYVAL b N f t \054 0	 0 1016 int8in int8out int8recv int8send - - - d p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 20 (	int8	   PGNSP PGUID	8 FLOAT8PASSBYVAL b N f t \054 0	 0 1016 int8in int8out int8recv int8send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("~18 digit integer, 8-byte storage");
 #define INT8OID			20
 
-DATA(insert OID = 21 (	int2	   PGNSP PGUID	2 t b N f t \054 0	 0 1005 int2in int2out int2recv int2send - - - s p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 21 (	int2	   PGNSP PGUID	2 t b N f t \054 0	 0 1005 int2in int2out int2recv int2send - - - s p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("-32 thousand to 32 thousand, 2-byte storage");
 #define INT2OID			21
 
-DATA(insert OID = 22 (	int2vector PGNSP PGUID -1 f b A f t \054 0	21 1006 int2vectorin int2vectorout int2vectorrecv int2vectorsend - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 22 (	int2vector PGNSP PGUID -1 f b A f t \054 0	21 1006 int2vectorin int2vectorout int2vectorrecv int2vectorsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("array of int2, used in system tables");
 #define INT2VECTOROID	22
 
-DATA(insert OID = 23 (	int4	   PGNSP PGUID	4 t b N f t \054 0	 0 1007 int4in int4out int4recv int4send - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 23 (	int4	   PGNSP PGUID	4 t b N f t \054 0	 0 1007 int4in int4out int4recv int4send - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("-2 billion to 2 billion integer, 4-byte storage");
 #define INT4OID			23
 
-DATA(insert OID = 24 (	regproc    PGNSP PGUID	4 t b N f t \054 0	 0 1008 regprocin regprocout regprocrecv regprocsend - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 24 (	regproc    PGNSP PGUID	4 t b N f t \054 0	 0 1008 regprocin regprocout regprocrecv regprocsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("registered procedure");
 #define REGPROCOID		24
 
-DATA(insert OID = 25 (	text	   PGNSP PGUID -1 f b S t t \054 0	0 1009 textin textout textrecv textsend - - - i x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 25 (	text	   PGNSP PGUID -1 f b S t t \054 0	0 1009 textin textout textrecv textsend - - - i x f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("variable-length string, no limit specified");
 #define TEXTOID			25
 
-DATA(insert OID = 26 (	oid		   PGNSP PGUID	4 t b N t t \054 0	 0 1028 oidin oidout oidrecv oidsend - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 26 (	oid		   PGNSP PGUID	4 t b N t t \054 0	 0 1028 oidin oidout oidrecv oidsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("object identifier(oid), maximum 4 billion");
 #define OIDOID			26
 
-DATA(insert OID = 27 (	tid		   PGNSP PGUID	6 f b U f t \054 0	 0 1010 tidin tidout tidrecv tidsend - - - s p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 27 (	tid		   PGNSP PGUID	6 f b U f t \054 0	 0 1010 tidin tidout tidrecv tidsend - - - s p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("(block, offset), physical location of tuple");
 #define TIDOID		27
 
-DATA(insert OID = 28 (	xid		   PGNSP PGUID	4 t b U f t \054 0	 0 1011 xidin xidout xidrecv xidsend - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 28 (	xid		   PGNSP PGUID	4 t b U f t \054 0	 0 1011 xidin xidout xidrecv xidsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("transaction id");
 #define XIDOID 28
 
-DATA(insert OID = 29 (	cid		   PGNSP PGUID	4 t b U f t \054 0	 0 1012 cidin cidout cidrecv cidsend - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 29 (	cid		   PGNSP PGUID	4 t b U f t \054 0	 0 1012 cidin cidout cidrecv cidsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("command identifier type, sequence in transaction id");
 #define CIDOID 29
 
-DATA(insert OID = 30 (	oidvector  PGNSP PGUID -1 f b A f t \054 0	26 1013 oidvectorin oidvectorout oidvectorrecv oidvectorsend - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 30 (	oidvector  PGNSP PGUID -1 f b A f t \054 0	26 1013 oidvectorin oidvectorout oidvectorrecv oidvectorsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("array of oids, used in system tables");
 #define OIDVECTOROID	30
 
 /* hand-built rowtype entries for bootstrapped catalogs */
 /* NB: OIDs assigned here must match the BKI_ROWTYPE_OID declarations */
 
-DATA(insert OID = 71 (	pg_type			PGNSP PGUID -1 f c C f t \054 1247 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 75 (	pg_attribute	PGNSP PGUID -1 f c C f t \054 1249 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 81 (	pg_proc			PGNSP PGUID -1 f c C f t \054 1255 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 83 (	pg_class		PGNSP PGUID -1 f c C f t \054 1259 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 71 (	pg_type			PGNSP PGUID -1 f c C f t \054 1247 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 75 (	pg_attribute	PGNSP PGUID -1 f c C f t \054 1249 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 81 (	pg_proc			PGNSP PGUID -1 f c C f t \054 1255 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 83 (	pg_class		PGNSP PGUID -1 f c C f t \054 1259 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
 
 /* OIDS 100 - 199 */
-DATA(insert OID = 142 ( xml		   PGNSP PGUID -1 f b U f t \054 0 0 143 xml_in xml_out xml_recv xml_send - - - i x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 142 ( xml		   PGNSP PGUID -1 f b U f t \054 0 0 143 xml_in xml_out xml_recv xml_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("XML content");
 #define XMLOID 142
-DATA(insert OID = 143 ( _xml	   PGNSP PGUID -1 f b A f t \054 0 142 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 143 ( _xml	   PGNSP PGUID -1 f b A f t \054 0 142 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
 
 /* OIDS 200 - 299 */
 
-DATA(insert OID = 210 (  smgr	   PGNSP PGUID 2 t b U f t \054 0 0 0 smgrin smgrout - - - - - s p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 210 (  smgr	   PGNSP PGUID 2 t b U f t \054 0 0 0 smgrin smgrout - - - - - s p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("storage manager");
 
 /* OIDS 300 - 399 */
@@ -355,231 +369,231 @@ DESCR("storage manager");
 /* OIDS 500 - 599 */
 
 /* OIDS 600 - 699 */
-DATA(insert OID = 600 (  point	   PGNSP PGUID 16 f b G f t \054 0 701 1017 point_in point_out point_recv point_send - - - d p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 600 (  point	   PGNSP PGUID 16 f b G f t \054 0 701 1017 point_in point_out point_recv point_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("geometric point '(x, y)'");
 #define POINTOID		600
-DATA(insert OID = 601 (  lseg	   PGNSP PGUID 32 f b G f t \054 0 600 1018 lseg_in lseg_out lseg_recv lseg_send - - - d p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 601 (  lseg	   PGNSP PGUID 32 f b G f t \054 0 600 1018 lseg_in lseg_out lseg_recv lseg_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("geometric line segment '(pt1,pt2)'");
 #define LSEGOID			601
-DATA(insert OID = 602 (  path	   PGNSP PGUID -1 f b G f t \054 0 0 1019 path_in path_out path_recv path_send - - - d x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 602 (  path	   PGNSP PGUID -1 f b G f t \054 0 0 1019 path_in path_out path_recv path_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("geometric path '(pt1,...)'");
 #define PATHOID			602
-DATA(insert OID = 603 (  box	   PGNSP PGUID 32 f b G f t \073 0 600 1020 box_in box_out box_recv box_send - - - d p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 603 (  box	   PGNSP PGUID 32 f b G f t \073 0 600 1020 box_in box_out box_recv box_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("geometric box '(lower left,upper right)'");
 #define BOXOID			603
-DATA(insert OID = 604 (  polygon   PGNSP PGUID -1 f b G f t \054 0	 0 1027 poly_in poly_out poly_recv poly_send - - - d x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 604 (  polygon   PGNSP PGUID -1 f b G f t \054 0	 0 1027 poly_in poly_out poly_recv poly_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("geometric polygon '(pt1,...)'");
 #define POLYGONOID		604
 
-DATA(insert OID = 628 (  line	   PGNSP PGUID 32 f b G f t \054 0 701 629 line_in line_out line_recv line_send - - - d p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 628 (  line	   PGNSP PGUID 32 f b G f t \054 0 701 629 line_in line_out line_recv line_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("geometric line (not implemented)");
 #define LINEOID			628
-DATA(insert OID = 629 (  _line	   PGNSP PGUID	-1 f b A f t \054 0 628 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 629 (  _line	   PGNSP PGUID	-1 f b A f t \054 0 628 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("");
 
 /* OIDS 700 - 799 */
 
-DATA(insert OID = 700 (  float4    PGNSP PGUID	4 FLOAT4PASSBYVAL b N f t \054 0	 0 1021 float4in float4out float4recv float4send - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 700 (  float4    PGNSP PGUID	4 FLOAT4PASSBYVAL b N f t \054 0	 0 1021 float4in float4out float4recv float4send - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("single-precision floating point number, 4-byte storage");
 #define FLOAT4OID 700
-DATA(insert OID = 701 (  float8    PGNSP PGUID	8 FLOAT8PASSBYVAL b N t t \054 0	 0 1022 float8in float8out float8recv float8send - - - d p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 701 (  float8    PGNSP PGUID	8 FLOAT8PASSBYVAL b N t t \054 0	 0 1022 float8in float8out float8recv float8send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("double-precision floating point number, 8-byte storage");
 #define FLOAT8OID 701
-DATA(insert OID = 702 (  abstime   PGNSP PGUID	4 t b D f t \054 0	 0 1023 abstimein abstimeout abstimerecv abstimesend - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 702 (  abstime   PGNSP PGUID	4 t b D f t \054 0	 0 1023 abstimein abstimeout abstimerecv abstimesend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("absolute, limited-range date and time (Unix system time)");
 #define ABSTIMEOID		702
-DATA(insert OID = 703 (  reltime   PGNSP PGUID	4 t b T f t \054 0	 0 1024 reltimein reltimeout reltimerecv reltimesend - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 703 (  reltime   PGNSP PGUID	4 t b T f t \054 0	 0 1024 reltimein reltimeout reltimerecv reltimesend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("relative, limited-range time interval (Unix delta time)");
 #define RELTIMEOID		703
-DATA(insert OID = 704 (  tinterval PGNSP PGUID 12 f b T f t \054 0	 0 1025 tintervalin tintervalout tintervalrecv tintervalsend - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 704 (  tinterval PGNSP PGUID 12 f b T f t \054 0	 0 1025 tintervalin tintervalout tintervalrecv tintervalsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("(abstime,abstime), time interval");
 #define TINTERVALOID	704
-DATA(insert OID = 705 (  unknown   PGNSP PGUID -2 f b X f t \054 0	 0 0 unknownin unknownout unknownrecv unknownsend - - - c p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 705 (  unknown   PGNSP PGUID -2 f b X f t \054 0	 0 0 unknownin unknownout unknownrecv unknownsend - - - c p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("");
 #define UNKNOWNOID		705
 
-DATA(insert OID = 718 (  circle    PGNSP PGUID	24 f b G f t \054 0 0 719 circle_in circle_out circle_recv circle_send - - - d p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 718 (  circle    PGNSP PGUID	24 f b G f t \054 0 0 719 circle_in circle_out circle_recv circle_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("geometric circle '(center,radius)'");
 #define CIRCLEOID		718
-DATA(insert OID = 719 (  _circle   PGNSP PGUID	-1 f b A f t \054 0  718 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 790 (  money	   PGNSP PGUID	 8 FLOAT8PASSBYVAL b N f t \054 0 0 791 cash_in cash_out cash_recv cash_send - - - d p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 719 (  _circle   PGNSP PGUID	-1 f b A f t \054 0  718 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 790 (  money	   PGNSP PGUID	 8 FLOAT8PASSBYVAL b N f t \054 0 0 791 cash_in cash_out cash_recv cash_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("monetary amounts, $d,ddd.cc");
 #define CASHOID 790
-DATA(insert OID = 791 (  _money    PGNSP PGUID	-1 f b A f t \054 0  790 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 791 (  _money    PGNSP PGUID	-1 f b A f t \054 0  790 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
 
 /* OIDS 800 - 899 */
-DATA(insert OID = 829 ( macaddr    PGNSP PGUID	6 f b U f t \054 0 0 1040 macaddr_in macaddr_out macaddr_recv macaddr_send - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 829 ( macaddr    PGNSP PGUID	6 f b U f t \054 0 0 1040 macaddr_in macaddr_out macaddr_recv macaddr_send - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("XX:XX:XX:XX:XX:XX, MAC address");
 #define MACADDROID 829
-DATA(insert OID = 869 ( inet	   PGNSP PGUID	-1 f b I t t \054 0 0 1041 inet_in inet_out inet_recv inet_send - - - i m f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 869 ( inet	   PGNSP PGUID	-1 f b I t t \054 0 0 1041 inet_in inet_out inet_recv inet_send - - - i m f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("IP address/netmask, host address, netmask optional");
 #define INETOID 869
-DATA(insert OID = 650 ( cidr	   PGNSP PGUID	-1 f b I f t \054 0 0 651 cidr_in cidr_out cidr_recv cidr_send - - - i m f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 650 ( cidr	   PGNSP PGUID	-1 f b I f t \054 0 0 651 cidr_in cidr_out cidr_recv cidr_send - - - i m f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("network IP address/netmask, network address");
 #define CIDROID 650
 
 /* OIDS 900 - 999 */
 
 /* OIDS 1000 - 1099 */
-DATA(insert OID = 1000 (  _bool		 PGNSP PGUID -1 f b A f t \054 0	16 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1001 (  _bytea	 PGNSP PGUID -1 f b A f t \054 0	17 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1002 (  _char		 PGNSP PGUID -1 f b A f t \054 0	18 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1003 (  _name		 PGNSP PGUID -1 f b A f t \054 0	19 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1005 (  _int2		 PGNSP PGUID -1 f b A f t \054 0	21 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1006 (  _int2vector PGNSP PGUID -1 f b A f t \054 0	22 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1007 (  _int4		 PGNSP PGUID -1 f b A f t \054 0	23 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 1000 (  _bool		 PGNSP PGUID -1 f b A f t \054 0	16 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1001 (  _bytea	 PGNSP PGUID -1 f b A f t \054 0	17 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1002 (  _char		 PGNSP PGUID -1 f b A f t \054 0	18 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1003 (  _name		 PGNSP PGUID -1 f b A f t \054 0	19 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1005 (  _int2		 PGNSP PGUID -1 f b A f t \054 0	21 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1006 (  _int2vector PGNSP PGUID -1 f b A f t \054 0	22 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1007 (  _int4		 PGNSP PGUID -1 f b A f t \054 0	23 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
 #define INT4ARRAYOID		1007
-DATA(insert OID = 1008 (  _regproc	 PGNSP PGUID -1 f b A f t \054 0	24 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1009 (  _text		 PGNSP PGUID -1 f b A f t \054 0	25 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 1008 (  _regproc	 PGNSP PGUID -1 f b A f t \054 0	24 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1009 (  _text		 PGNSP PGUID -1 f b A f t \054 0	25 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
 #define TEXTARRAYOID		1009
-DATA(insert OID = 1028 (  _oid		 PGNSP PGUID -1 f b A f t \054 0	26 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1010 (  _tid		 PGNSP PGUID -1 f b A f t \054 0	27 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1011 (  _xid		 PGNSP PGUID -1 f b A f t \054 0	28 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1012 (  _cid		 PGNSP PGUID -1 f b A f t \054 0	29 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1013 (  _oidvector PGNSP PGUID -1 f b A f t \054 0	30 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1014 (  _bpchar	 PGNSP PGUID -1 f b A f t \054 0 1042 0 array_in array_out array_recv array_send bpchartypmodin bpchartypmodout - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1015 (  _varchar	 PGNSP PGUID -1 f b A f t \054 0 1043 0 array_in array_out array_recv array_send varchartypmodin varchartypmodout - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1016 (  _int8		 PGNSP PGUID -1 f b A f t \054 0	20 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1017 (  _point	 PGNSP PGUID -1 f b A f t \054 0 600 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1018 (  _lseg		 PGNSP PGUID -1 f b A f t \054 0 601 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1019 (  _path		 PGNSP PGUID -1 f b A f t \054 0 602 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1020 (  _box		 PGNSP PGUID -1 f b A f t \073 0 603 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1021 (  _float4	 PGNSP PGUID -1 f b A f t \054 0 700 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 1028 (  _oid		 PGNSP PGUID -1 f b A f t \054 0	26 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1010 (  _tid		 PGNSP PGUID -1 f b A f t \054 0	27 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1011 (  _xid		 PGNSP PGUID -1 f b A f t \054 0	28 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1012 (  _cid		 PGNSP PGUID -1 f b A f t \054 0	29 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1013 (  _oidvector PGNSP PGUID -1 f b A f t \054 0	30 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1014 (  _bpchar	 PGNSP PGUID -1 f b A f t \054 0 1042 0 array_in array_out array_recv array_send bpchartypmodin bpchartypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1015 (  _varchar	 PGNSP PGUID -1 f b A f t \054 0 1043 0 array_in array_out array_recv array_send varchartypmodin varchartypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1016 (  _int8		 PGNSP PGUID -1 f b A f t \054 0	20 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1017 (  _point	 PGNSP PGUID -1 f b A f t \054 0 600 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1018 (  _lseg		 PGNSP PGUID -1 f b A f t \054 0 601 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1019 (  _path		 PGNSP PGUID -1 f b A f t \054 0 602 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1020 (  _box		 PGNSP PGUID -1 f b A f t \073 0 603 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1021 (  _float4	 PGNSP PGUID -1 f b A f t \054 0 700 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
 #define FLOAT4ARRAYOID 1021
-DATA(insert OID = 1022 (  _float8	 PGNSP PGUID -1 f b A f t \054 0 701 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1023 (  _abstime	 PGNSP PGUID -1 f b A f t \054 0 702 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1024 (  _reltime	 PGNSP PGUID -1 f b A f t \054 0 703 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1025 (  _tinterval PGNSP PGUID -1 f b A f t \054 0 704 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1027 (  _polygon	 PGNSP PGUID -1 f b A f t \054 0 604 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1033 (  aclitem	 PGNSP PGUID 12 f b U f t \054 0 0 1034 aclitemin aclitemout - - - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 1022 (  _float8	 PGNSP PGUID -1 f b A f t \054 0 701 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1023 (  _abstime	 PGNSP PGUID -1 f b A f t \054 0 702 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1024 (  _reltime	 PGNSP PGUID -1 f b A f t \054 0 703 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1025 (  _tinterval PGNSP PGUID -1 f b A f t \054 0 704 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1027 (  _polygon	 PGNSP PGUID -1 f b A f t \054 0 604 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1033 (  aclitem	 PGNSP PGUID 12 f b U f t \054 0 0 1034 aclitemin aclitemout - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("access control list");
 #define ACLITEMOID		1033
-DATA(insert OID = 1034 (  _aclitem	 PGNSP PGUID -1 f b A f t \054 0 1033 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1040 (  _macaddr	 PGNSP PGUID -1 f b A f t \054 0  829 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1041 (  _inet		 PGNSP PGUID -1 f b A f t \054 0  869 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 651  (  _cidr		 PGNSP PGUID -1 f b A f t \054 0  650 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1263 (  _cstring	 PGNSP PGUID -1 f b A f t \054 0 2275 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 1034 (  _aclitem	 PGNSP PGUID -1 f b A f t \054 0 1033 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1040 (  _macaddr	 PGNSP PGUID -1 f b A f t \054 0  829 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1041 (  _inet		 PGNSP PGUID -1 f b A f t \054 0  869 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 651  (  _cidr		 PGNSP PGUID -1 f b A f t \054 0  650 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1263 (  _cstring	 PGNSP PGUID -1 f b A f t \054 0 2275 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
 #define CSTRINGARRAYOID		1263
 
-DATA(insert OID = 1042 ( bpchar		 PGNSP PGUID -1 f b S f t \054 0	0 1014 bpcharin bpcharout bpcharrecv bpcharsend bpchartypmodin bpchartypmodout - i x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 1042 ( bpchar		 PGNSP PGUID -1 f b S f t \054 0	0 1014 bpcharin bpcharout bpcharrecv bpcharsend bpchartypmodin bpchartypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("char(length), blank-padded string, fixed storage length");
 #define BPCHAROID		1042
-DATA(insert OID = 1043 ( varchar	 PGNSP PGUID -1 f b S f t \054 0	0 1015 varcharin varcharout varcharrecv varcharsend varchartypmodin varchartypmodout - i x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 1043 ( varchar	 PGNSP PGUID -1 f b S f t \054 0	0 1015 varcharin varcharout varcharrecv varcharsend varchartypmodin varchartypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("varchar(length), non-blank-padded string, variable storage length");
 #define VARCHAROID		1043
 
-DATA(insert OID = 1082 ( date		 PGNSP PGUID	4 t b D f t \054 0	0 1182 date_in date_out date_recv date_send - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 1082 ( date		 PGNSP PGUID	4 t b D f t \054 0	0 1182 date_in date_out date_recv date_send - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("date");
 #define DATEOID			1082
-DATA(insert OID = 1083 ( time		 PGNSP PGUID	8 FLOAT8PASSBYVAL b D f t \054 0	0 1183 time_in time_out time_recv time_send timetypmodin timetypmodout - d p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 1083 ( time		 PGNSP PGUID	8 FLOAT8PASSBYVAL b D f t \054 0	0 1183 time_in time_out time_recv time_send timetypmodin timetypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("time of day");
 #define TIMEOID			1083
 
 /* OIDS 1100 - 1199 */
-DATA(insert OID = 1114 ( timestamp	 PGNSP PGUID	8 FLOAT8PASSBYVAL b D f t \054 0	0 1115 timestamp_in timestamp_out timestamp_recv timestamp_send timestamptypmodin timestamptypmodout - d p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 1114 ( timestamp	 PGNSP PGUID	8 FLOAT8PASSBYVAL b D f t \054 0	0 1115 timestamp_in timestamp_out timestamp_recv timestamp_send timestamptypmodin timestamptypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("date and time");
 #define TIMESTAMPOID	1114
-DATA(insert OID = 1115 ( _timestamp  PGNSP PGUID	-1 f b A f t \054 0 1114 0 array_in array_out array_recv array_send timestamptypmodin timestamptypmodout - d x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1182 ( _date		 PGNSP PGUID	-1 f b A f t \054 0 1082 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1183 ( _time		 PGNSP PGUID	-1 f b A f t \054 0 1083 0 array_in array_out array_recv array_send timetypmodin timetypmodout - d x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1184 ( timestamptz PGNSP PGUID	8 FLOAT8PASSBYVAL b D t t \054 0	0 1185 timestamptz_in timestamptz_out timestamptz_recv timestamptz_send timestamptztypmodin timestamptztypmodout - d p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 1115 ( _timestamp  PGNSP PGUID	-1 f b A f t \054 0 1114 0 array_in array_out array_recv array_send timestamptypmodin timestamptypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1182 ( _date		 PGNSP PGUID	-1 f b A f t \054 0 1082 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1183 ( _time		 PGNSP PGUID	-1 f b A f t \054 0 1083 0 array_in array_out array_recv array_send timetypmodin timetypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1184 ( timestamptz PGNSP PGUID	8 FLOAT8PASSBYVAL b D t t \054 0	0 1185 timestamptz_in timestamptz_out timestamptz_recv timestamptz_send timestamptztypmodin timestamptztypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("date and time with time zone");
 #define TIMESTAMPTZOID	1184
-DATA(insert OID = 1185 ( _timestamptz PGNSP PGUID -1 f b A f t \054 0	1184 0 array_in array_out array_recv array_send timestamptztypmodin timestamptztypmodout - d x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1186 ( interval	 PGNSP PGUID 16 f b T t t \054 0	0 1187 interval_in interval_out interval_recv interval_send intervaltypmodin intervaltypmodout - d p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 1185 ( _timestamptz PGNSP PGUID -1 f b A f t \054 0	1184 0 array_in array_out array_recv array_send timestamptztypmodin timestamptztypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1186 ( interval	 PGNSP PGUID 16 f b T t t \054 0	0 1187 interval_in interval_out interval_recv interval_send intervaltypmodin intervaltypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("@ <number> <units>, time interval");
 #define INTERVALOID		1186
-DATA(insert OID = 1187 ( _interval	 PGNSP PGUID	-1 f b A f t \054 0 1186 0 array_in array_out array_recv array_send intervaltypmodin intervaltypmodout - d x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 1187 ( _interval	 PGNSP PGUID	-1 f b A f t \054 0 1186 0 array_in array_out array_recv array_send intervaltypmodin intervaltypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
 
 /* OIDS 1200 - 1299 */
-DATA(insert OID = 1231 (  _numeric	 PGNSP PGUID -1 f b A f t \054 0	1700 0 array_in array_out array_recv array_send numerictypmodin numerictypmodout - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1266 ( timetz		 PGNSP PGUID 12 f b D f t \054 0	0 1270 timetz_in timetz_out timetz_recv timetz_send timetztypmodin timetztypmodout - d p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 1231 (  _numeric	 PGNSP PGUID -1 f b A f t \054 0	1700 0 array_in array_out array_recv array_send numerictypmodin numerictypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1266 ( timetz		 PGNSP PGUID 12 f b D f t \054 0	0 1270 timetz_in timetz_out timetz_recv timetz_send timetztypmodin timetztypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("time of day with time zone");
 #define TIMETZOID		1266
-DATA(insert OID = 1270 ( _timetz	 PGNSP PGUID -1 f b A f t \054 0	1266 0 array_in array_out array_recv array_send timetztypmodin timetztypmodout - d x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 1270 ( _timetz	 PGNSP PGUID -1 f b A f t \054 0	1266 0 array_in array_out array_recv array_send timetztypmodin timetztypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
 
 /* OIDS 1500 - 1599 */
-DATA(insert OID = 1560 ( bit		 PGNSP PGUID -1 f b V f t \054 0	0 1561 bit_in bit_out bit_recv bit_send bittypmodin bittypmodout - i x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 1560 ( bit		 PGNSP PGUID -1 f b V f t \054 0	0 1561 bit_in bit_out bit_recv bit_send bittypmodin bittypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("fixed-length bit string");
 #define BITOID	 1560
-DATA(insert OID = 1561 ( _bit		 PGNSP PGUID -1 f b A f t \054 0	1560 0 array_in array_out array_recv array_send bittypmodin bittypmodout - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 1562 ( varbit		 PGNSP PGUID -1 f b V t t \054 0	0 1563 varbit_in varbit_out varbit_recv varbit_send varbittypmodin varbittypmodout - i x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 1561 ( _bit		 PGNSP PGUID -1 f b A f t \054 0	1560 0 array_in array_out array_recv array_send bittypmodin bittypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 1562 ( varbit		 PGNSP PGUID -1 f b V t t \054 0	0 1563 varbit_in varbit_out varbit_recv varbit_send varbittypmodin varbittypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("variable-length bit string");
 #define VARBITOID	  1562
-DATA(insert OID = 1563 ( _varbit	 PGNSP PGUID -1 f b A f t \054 0	1562 0 array_in array_out array_recv array_send varbittypmodin varbittypmodout - i x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 1563 ( _varbit	 PGNSP PGUID -1 f b A f t \054 0	1562 0 array_in array_out array_recv array_send varbittypmodin varbittypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
 
 /* OIDS 1600 - 1699 */
 
 /* OIDS 1700 - 1799 */
-DATA(insert OID = 1700 ( numeric	   PGNSP PGUID -1 f b N f t \054 0	0 1231 numeric_in numeric_out numeric_recv numeric_send numerictypmodin numerictypmodout - i m f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 1700 ( numeric	   PGNSP PGUID -1 f b N f t \054 0	0 1231 numeric_in numeric_out numeric_recv numeric_send numerictypmodin numerictypmodout - i m f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("numeric(precision, decimal), arbitrary precision number");
 #define NUMERICOID		1700
 
-DATA(insert OID = 1790 ( refcursor	   PGNSP PGUID -1 f b U f t \054 0	0 2201 textin textout textrecv textsend - - - i x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 1790 ( refcursor	   PGNSP PGUID -1 f b U f t \054 0	0 2201 textin textout textrecv textsend - - - i x f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("reference to cursor (portal name)");
 #define REFCURSOROID	1790
 
 /* OIDS 2200 - 2299 */
-DATA(insert OID = 2201 ( _refcursor    PGNSP PGUID -1 f b A f t \054 0 1790 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 2201 ( _refcursor    PGNSP PGUID -1 f b A f t \054 0 1790 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
 
-DATA(insert OID = 2202 ( regprocedure  PGNSP PGUID	4 t b N f t \054 0	 0 2207 regprocedurein regprocedureout regprocedurerecv regproceduresend - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 2202 ( regprocedure  PGNSP PGUID	4 t b N f t \054 0	 0 2207 regprocedurein regprocedureout regprocedurerecv regproceduresend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("registered procedure (with args)");
 #define REGPROCEDUREOID 2202
 
-DATA(insert OID = 2203 ( regoper	   PGNSP PGUID	4 t b N f t \054 0	 0 2208 regoperin regoperout regoperrecv regopersend - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 2203 ( regoper	   PGNSP PGUID	4 t b N f t \054 0	 0 2208 regoperin regoperout regoperrecv regopersend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("registered operator");
 #define REGOPEROID		2203
 
-DATA(insert OID = 2204 ( regoperator   PGNSP PGUID	4 t b N f t \054 0	 0 2209 regoperatorin regoperatorout regoperatorrecv regoperatorsend - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 2204 ( regoperator   PGNSP PGUID	4 t b N f t \054 0	 0 2209 regoperatorin regoperatorout regoperatorrecv regoperatorsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("registered operator (with args)");
 #define REGOPERATOROID	2204
 
-DATA(insert OID = 2205 ( regclass	   PGNSP PGUID	4 t b N f t \054 0	 0 2210 regclassin regclassout regclassrecv regclasssend - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 2205 ( regclass	   PGNSP PGUID	4 t b N f t \054 0	 0 2210 regclassin regclassout regclassrecv regclasssend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("registered class");
 #define REGCLASSOID		2205
 
-DATA(insert OID = 2206 ( regtype	   PGNSP PGUID	4 t b N f t \054 0	 0 2211 regtypein regtypeout regtyperecv regtypesend - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 2206 ( regtype	   PGNSP PGUID	4 t b N f t \054 0	 0 2211 regtypein regtypeout regtyperecv regtypesend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("registered type");
 #define REGTYPEOID		2206
 
-DATA(insert OID = 2207 ( _regprocedure PGNSP PGUID -1 f b A f t \054 0 2202 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 2208 ( _regoper	   PGNSP PGUID -1 f b A f t \054 0 2203 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 2209 ( _regoperator  PGNSP PGUID -1 f b A f t \054 0 2204 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 2210 ( _regclass	   PGNSP PGUID -1 f b A f t \054 0 2205 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 2211 ( _regtype	   PGNSP PGUID -1 f b A f t \054 0 2206 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 2207 ( _regprocedure PGNSP PGUID -1 f b A f t \054 0 2202 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 2208 ( _regoper	   PGNSP PGUID -1 f b A f t \054 0 2203 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 2209 ( _regoperator  PGNSP PGUID -1 f b A f t \054 0 2204 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 2210 ( _regclass	   PGNSP PGUID -1 f b A f t \054 0 2205 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 2211 ( _regtype	   PGNSP PGUID -1 f b A f t \054 0 2206 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
 #define REGTYPEARRAYOID 2211
 
 /* uuid */
-DATA(insert OID = 2950 ( uuid			PGNSP PGUID 16 f b U f t \054 0 0 2951 uuid_in uuid_out uuid_recv uuid_send - - - c p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 2950 ( uuid			PGNSP PGUID 16 f b U f t \054 0 0 2951 uuid_in uuid_out uuid_recv uuid_send - - - c p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("UUID datatype");
-DATA(insert OID = 2951 ( _uuid			PGNSP PGUID -1 f b A f t \054 0 2950 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 2951 ( _uuid			PGNSP PGUID -1 f b A f t \054 0 2950 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
 
 /* text search */
-DATA(insert OID = 3614 ( tsvector		PGNSP PGUID -1 f b U f t \054 0 0 3643 tsvectorin tsvectorout tsvectorrecv tsvectorsend - - ts_typanalyze i x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 3614 ( tsvector		PGNSP PGUID -1 f b U f t \054 0 0 3643 tsvectorin tsvectorout tsvectorrecv tsvectorsend - - ts_typanalyze i x f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("text representation for text search");
 #define TSVECTOROID		3614
-DATA(insert OID = 3642 ( gtsvector		PGNSP PGUID -1 f b U f t \054 0 0 3644 gtsvectorin gtsvectorout - - - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 3642 ( gtsvector		PGNSP PGUID -1 f b U f t \054 0 0 3644 gtsvectorin gtsvectorout - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("GiST index internal text representation for text search");
 #define GTSVECTOROID	3642
-DATA(insert OID = 3615 ( tsquery		PGNSP PGUID -1 f b U f t \054 0 0 3645 tsqueryin tsqueryout tsqueryrecv tsquerysend - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 3615 ( tsquery		PGNSP PGUID -1 f b U f t \054 0 0 3645 tsqueryin tsqueryout tsqueryrecv tsquerysend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("query representation for text search");
 #define TSQUERYOID		3615
-DATA(insert OID = 3734 ( regconfig		PGNSP PGUID 4 t b N f t \054 0 0 3735 regconfigin regconfigout regconfigrecv regconfigsend - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 3734 ( regconfig		PGNSP PGUID 4 t b N f t \054 0 0 3735 regconfigin regconfigout regconfigrecv regconfigsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("registered text search configuration");
 #define REGCONFIGOID	3734
-DATA(insert OID = 3769 ( regdictionary	PGNSP PGUID 4 t b N f t \054 0 0 3770 regdictionaryin regdictionaryout regdictionaryrecv regdictionarysend - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 3769 ( regdictionary	PGNSP PGUID 4 t b N f t \054 0 0 3770 regdictionaryin regdictionaryout regdictionaryrecv regdictionarysend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("registered text search dictionary");
 #define REGDICTIONARYOID	3769
 
-DATA(insert OID = 3643 ( _tsvector		PGNSP PGUID -1 f b A f t \054 0 3614 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 3644 ( _gtsvector		PGNSP PGUID -1 f b A f t \054 0 3642 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 3645 ( _tsquery		PGNSP PGUID -1 f b A f t \054 0 3615 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 3735 ( _regconfig		PGNSP PGUID -1 f b A f t \054 0 3734 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
-DATA(insert OID = 3770 ( _regdictionary PGNSP PGUID -1 f b A f t \054 0 3769 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 3643 ( _tsvector		PGNSP PGUID -1 f b A f t \054 0 3614 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 3644 ( _gtsvector		PGNSP PGUID -1 f b A f t \054 0 3642 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 3645 ( _tsquery		PGNSP PGUID -1 f b A f t \054 0 3615 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 3735 ( _regconfig		PGNSP PGUID -1 f b A f t \054 0 3734 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
+DATA(insert OID = 3770 ( _regdictionary PGNSP PGUID -1 f b A f t \054 0 3769 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
 
-DATA(insert OID = 2970 ( txid_snapshot	PGNSP PGUID -1 f b U f t \054 0 0 2949 txid_snapshot_in txid_snapshot_out txid_snapshot_recv txid_snapshot_send - - - d x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 2970 ( txid_snapshot	PGNSP PGUID -1 f b U f t \054 0 0 2949 txid_snapshot_in txid_snapshot_out txid_snapshot_recv txid_snapshot_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
 DESCR("txid snapshot");
-DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
 
 /*
  * pseudo-types
@@ -594,31 +608,31 @@ DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 a
  * but there is now support for it in records and arrays.  Perhaps we should
  * just treat it as a regular base type?
  */
-DATA(insert OID = 2249 ( record			PGNSP PGUID -1 f p P f t \054 0 0 2287 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 2249 ( record			PGNSP PGUID -1 f p P f t \054 0 0 2287 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
 #define RECORDOID		2249
-DATA(insert OID = 2287 ( _record		PGNSP PGUID -1 f p P f t \054 0 2249 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 2287 ( _record		PGNSP PGUID -1 f p P f t \054 0 2249 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
 #define RECORDARRAYOID	2287
-DATA(insert OID = 2275 ( cstring		PGNSP PGUID -2 f p P f t \054 0 0 1263 cstring_in cstring_out cstring_recv cstring_send - - - c p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 2275 ( cstring		PGNSP PGUID -2 f p P f t \054 0 0 1263 cstring_in cstring_out cstring_recv cstring_send - - - c p f 0 -1 0 -1 f _null_ _null_ ));
 #define CSTRINGOID		2275
-DATA(insert OID = 2276 ( any			PGNSP PGUID  4 t p P f t \054 0 0 0 any_in any_out - - - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 2276 ( any			PGNSP PGUID  4 t p P f t \054 0 0 0 any_in any_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 #define ANYOID			2276
-DATA(insert OID = 2277 ( anyarray		PGNSP PGUID -1 f p P f t \054 0 0 0 anyarray_in anyarray_out anyarray_recv anyarray_send - - - d x f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 2277 ( anyarray		PGNSP PGUID -1 f p P f t \054 0 0 0 anyarray_in anyarray_out anyarray_recv anyarray_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
 #define ANYARRAYOID		2277
-DATA(insert OID = 2278 ( void			PGNSP PGUID  4 t p P f t \054 0 0 0 void_in void_out - - - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 2278 ( void			PGNSP PGUID  4 t p P f t \054 0 0 0 void_in void_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 #define VOIDOID			2278
-DATA(insert OID = 2279 ( trigger		PGNSP PGUID  4 t p P f t \054 0 0 0 trigger_in trigger_out - - - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 2279 ( trigger		PGNSP PGUID  4 t p P f t \054 0 0 0 trigger_in trigger_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 #define TRIGGEROID		2279
-DATA(insert OID = 2280 ( language_handler	PGNSP PGUID  4 t p P f t \054 0 0 0 language_handler_in language_handler_out - - - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 2280 ( language_handler	PGNSP PGUID  4 t p P f t \054 0 0 0 language_handler_in language_handler_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 #define LANGUAGE_HANDLEROID		2280
-DATA(insert OID = 2281 ( internal		PGNSP PGUID  SIZEOF_POINTER t p P f t \054 0 0 0 internal_in internal_out - - - - - ALIGNOF_POINTER p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 2281 ( internal		PGNSP PGUID  SIZEOF_POINTER t p P f t \054 0 0 0 internal_in internal_out - - - - - ALIGNOF_POINTER p f 0 -1 0 -1 f _null_ _null_ ));
 #define INTERNALOID		2281
-DATA(insert OID = 2282 ( opaque			PGNSP PGUID  4 t p P f t \054 0 0 0 opaque_in opaque_out - - - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 2282 ( opaque			PGNSP PGUID  4 t p P f t \054 0 0 0 opaque_in opaque_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 #define OPAQUEOID		2282
-DATA(insert OID = 2283 ( anyelement		PGNSP PGUID  4 t p P f t \054 0 0 0 anyelement_in anyelement_out - - - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 2283 ( anyelement		PGNSP PGUID  4 t p P f t \054 0 0 0 anyelement_in anyelement_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 #define ANYELEMENTOID	2283
-DATA(insert OID = 2776 ( anynonarray	PGNSP PGUID  4 t p P f t \054 0 0 0 anynonarray_in anynonarray_out - - - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 2776 ( anynonarray	PGNSP PGUID  4 t p P f t \054 0 0 0 anynonarray_in anynonarray_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 #define ANYNONARRAYOID	2776
-DATA(insert OID = 3500 ( anyenum		PGNSP PGUID  4 t p P f t \054 0 0 0 anyenum_in anyenum_out - - - - - i p f 0 -1 0 _null_ _null_ ));
+DATA(insert OID = 3500 ( anyenum		PGNSP PGUID  4 t p P f t \054 0 0 0 anyenum_in anyenum_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
 #define ANYENUMOID		3500
 
 
diff --git a/src/include/catalog/pg_type_fn.h b/src/include/catalog/pg_type_fn.h
index e5b5a96..1769af1 100644
--- a/src/include/catalog/pg_type_fn.h
+++ b/src/include/catalog/pg_type_fn.h
@@ -50,7 +50,9 @@ extern Oid TypeCreate(Oid newTypeOid,
 		   char storage,
 		   int32 typeMod,
 		   int32 typNDims,
-		   bool typeNotNull);
+    	   bool typeNotNull,
+		   int32 typeNLabels,
+		   bool typeSorted);
 
 extern void GenerateTypeDependencies(Oid typeNamespace,
 						 Oid typeObjectId,
diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h
index 809143c..fb890b8 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 ebec0eb..b61938c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -346,6 +346,7 @@ typedef enum NodeTag
 	T_AlterUserMappingStmt,
 	T_DropUserMappingStmt,
 	T_AlterTableSpaceOptionsStmt,
+	T_AlterEnumStmt,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ce3da66..d06dfb8 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2171,6 +2171,20 @@ typedef struct CreateEnumStmt
 
 
 /* ----------------------
+ *		Alter Type Statement, enum types
+ * ----------------------
+ */
+typedef struct AlterEnumStmt
+{
+	NodeTag		type;
+	List	   *typeName;		/* qualified name (list of Value strings) */
+	char	   *newVal;			/* new enum value */
+	char	   *newValNeighbour;/* neighbouring enum value */
+	bool	    newValIsAfter;	/* new enum value is after neighbour? */
+} AlterEnumStmt;
+
+
+/* ----------------------
  *		Create View Statement
  * ----------------------
  */
diff --git a/src/test/regress/expected/enum.out b/src/test/regress/expected/enum.out
index 56240c0..ae595af 100644
--- a/src/test/regress/expected/enum.out
+++ b/src/test/regress/expected/enum.out
@@ -25,6 +25,84 @@ 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 typnlabels, typsorted
+FROM pg_type
+WHERE oid = 'planets'::regtype;
+ typnlabels | typsorted 
+------------+-----------
+          4 | t
+(1 row)
+
+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 typnlabels, typsorted
+FROM pg_type
+WHERE oid = 'planets'::regtype;
+ typnlabels | typsorted 
+------------+-----------
+          8 | f
+(1 row)
+
+SELECT enumlabel, enumsortorder 
+FROM pg_enum 
+WHERE enumtypid = 'planets'::regtype
+ORDER by 2;
+ enumlabel | enumsortorder 
+-----------+---------------
+ mercury   |             1
+ venus     |             2
+ earth     |             3
+ mars      |             4
+ jupiter   |             5
+ saturn    |             6
+ uranus    |             7
+ neptune   |             8
+(8 rows)
+
+select 'mars'::planets > 'mercury' as using_sortorder;
+ using_sortorder 
+-----------------
+ t
+(1 row)
+
+-- 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 label.
+DROP TYPE planets;
+--
 -- Basic table creation, row selection
 --
 CREATE TABLE enumtest (col rainbow);
@@ -403,7 +481,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 | enumlabel | enumsortorder 
+-----------+-----------+---------------
 (0 rows)
 
diff --git a/src/test/regress/sql/enum.sql b/src/test/regress/sql/enum.sql
index 387e8e7..0e240b2 100644
--- a/src/test/regress/sql/enum.sql
+++ b/src/test/regress/sql/enum.sql
@@ -16,6 +16,54 @@ 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 typnlabels, typsorted
+FROM pg_type
+WHERE oid = 'planets'::regtype;
+
+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 typnlabels, typsorted
+FROM pg_type
+WHERE oid = 'planets'::regtype;
+
+SELECT enumlabel, enumsortorder 
+FROM pg_enum 
+WHERE enumtypid = 'planets'::regtype
+ORDER by 2;
+
+select 'mars'::planets > 'mercury' as using_sortorder;
+
+-- errors for adding labels 
+ALTER TYPE planets ADD 
+	  'plutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutopluto';
+
+ALTER TYPE planets ADD 'pluto' AFTER 'zeus';
+
+DROP TYPE planets;
+--
 -- Basic table creation, row selection
 --
 CREATE TABLE enumtest (col rainbow);
#30Andrew Dunstan
andrew@dunslane.net
In reply to: Andrew Dunstan (#29)
1 attachment(s)
Re: WIP: extensible enums

On 08/26/2010 05:24 AM, Andrew Dunstan wrote:

On 08/25/2010 03:29 AM, Andrew Dunstan wrote:

I've just been taking another look at this suggestion. I think it
will work quite cleanly. As long as we add the enums in the correct
order it should just do the Right Thing (tm).

To answer your other question, Oid wraparound will not be a problem.

Will get coding.

Revised patch with pg_dump and pg_upgrade support is attached.

This time in context diff format.

cheers

andrew

Attachments:

venum2.patchtext/x-patch; name=venum2.patchDownload
*** a/contrib/pg_upgrade/function.c
--- b/contrib/pg_upgrade/function.c
***************
*** 58,82 **** install_support_functions(migratorContext *ctx)
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 				"		binary_upgrade.set_next_heap_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_toast_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_index_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			 "		binary_upgrade.add_pg_enum_label(OID, OID, NAME) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
--- 58,82 ----
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 					 "		binary_upgrade.set_next_pg_enum_oid(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 				"		binary_upgrade.set_next_heap_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_toast_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_index_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
*** a/contrib/pg_upgrade_support/pg_upgrade_support.c
--- b/contrib/pg_upgrade_support/pg_upgrade_support.c
***************
*** 27,32 **** PG_MODULE_MAGIC;
--- 27,33 ----
  extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
  extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_array_oid;
  extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_toast_oid;
+ extern PGDLLIMPORT Oid binary_upgrade_next_pg_enum_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;
***************
*** 34,51 **** extern PGDLLIMPORT Oid binary_upgrade_next_index_relfilenode;
  Datum set_next_pg_type_oid(PG_FUNCTION_ARGS);
  Datum set_next_pg_type_array_oid(PG_FUNCTION_ARGS);
  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);
  
  PG_FUNCTION_INFO_V1(set_next_pg_type_oid);
  PG_FUNCTION_INFO_V1(set_next_pg_type_array_oid);
  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);
  
  Datum
  set_next_pg_type_oid(PG_FUNCTION_ARGS)
--- 35,52 ----
  Datum set_next_pg_type_oid(PG_FUNCTION_ARGS);
  Datum set_next_pg_type_array_oid(PG_FUNCTION_ARGS);
  Datum set_next_pg_type_toast_oid(PG_FUNCTION_ARGS);
+ Datum set_next_pg_enum_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);
  
  PG_FUNCTION_INFO_V1(set_next_pg_type_oid);
  PG_FUNCTION_INFO_V1(set_next_pg_type_array_oid);
  PG_FUNCTION_INFO_V1(set_next_pg_type_toast_oid);
+ PG_FUNCTION_INFO_V1(set_next_pg_enum_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);
  
  Datum
  set_next_pg_type_oid(PG_FUNCTION_ARGS)
***************
*** 78,83 **** set_next_pg_type_toast_oid(PG_FUNCTION_ARGS)
--- 79,94 ----
  }
  
  Datum
+ set_next_pg_enum_oid(PG_FUNCTION_ARGS)
+ {
+ 	Oid			enumoid = PG_GETARG_OID(0);
+ 
+ 	binary_upgrade_next_pg_enum_oid = enumoid;
+ 
+ 	PG_RETURN_VOID();
+ }
+ 
+ Datum
  set_next_heap_relfilenode(PG_FUNCTION_ARGS)
  {
  	Oid			relfilenode = PG_GETARG_OID(0);
***************
*** 106,122 **** set_next_index_relfilenode(PG_FUNCTION_ARGS)
  
  	PG_RETURN_VOID();
  }
- 
- Datum
- add_pg_enum_label(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);
- 
- 	PG_RETURN_VOID();
- }
- 
--- 117,119 ----
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 854,860 **** AddNewRelationType(const char *typeName,
  				   'x',			/* fully TOASTable */
  				   -1,			/* typmod */
  				   0,			/* array dimensions for typBaseType */
! 				   false);		/* Type NOT NULL */
  }
  
  /* --------------------------------
--- 854,862 ----
  				   'x',			/* fully TOASTable */
  				   -1,			/* typmod */
  				   0,			/* array dimensions for typBaseType */
! 				   false,		/* Type NOT NULL */
! 				   -1,          /* no enum labels */
! 				   false);      /* type is not an enum, so not sorted */
  }
  
  /* --------------------------------
***************
*** 1086,1092 **** heap_create_with_catalog(const char *relname,
  				   'x',			/* fully TOASTable */
  				   -1,			/* typmod */
  				   0,			/* array dimensions for typBaseType */
! 				   false);		/* Type NOT NULL */
  
  		pfree(relarrayname);
  	}
--- 1088,1096 ----
  				   'x',			/* fully TOASTable */
  				   -1,			/* typmod */
  				   0,			/* array dimensions for typBaseType */
! 				   false,		/* Type NOT NULL */
! 				   -1,          /* no enum labels */
! 				   false);      /* type is not an enum, so not sorted */
  
  		pfree(relarrayname);
  	}
*** a/src/backend/catalog/pg_enum.c
--- b/src/backend/catalog/pg_enum.c
***************
*** 18,29 ****
--- 18,219 ----
  #include "catalog/catalog.h"
  #include "catalog/indexing.h"
  #include "catalog/pg_enum.h"
+ #include "catalog/pg_type.h"
+ #include "utils/lsyscache.h"
+ #include "utils/syscache.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
  #include "utils/rel.h"
  #include "utils/tqual.h"
  
  static int	oid_cmp(const void *p1, const void *p2);
+ static int	sort_order_cmp(const void *p1, const void *p2);
+ 
+ Oid      binary_upgrade_next_pg_enum_oid = InvalidOid;
+ 
+ /*
+  * 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.
+  *
+  * Returns true iff the labels are sorted by oid after the addition.
+  */
+ 
+ bool
+ AddEnumLabel(Oid enumTypeOid, 
+ 			 char *newVal, 
+ 			 char *neighbour, 
+ 			 bool newValIsAfter,
+ 			 int  nelems,
+ 			 bool elems_are_sorted)
+ {
+ 	Oid        newOid;
+ 	Relation   pg_enum;
+ 	TupleDesc	tupDesc;
+ 	Datum		values[Natts_pg_enum];
+ 	bool		nulls[Natts_pg_enum];
+ 	NameData	enumlabel;
+ 	HeapTuple   enum_tup;
+ 	bool        result = elems_are_sorted;
+ 	int         newelemorder;
+ 
+ 	/* 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)));
+ 
+ 	/* get a new OID for the label */
+ 	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
+ 
+ 	if (OidIsValid(binary_upgrade_next_pg_enum_oid))
+ 	{
+ 		if (neighbour != NULL)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("BEFORE/AFTER incompatible with binary upgrade.")));
+ 
+ 		newOid = binary_upgrade_next_pg_enum_oid;
+ 		binary_upgrade_next_pg_enum_oid = InvalidOid;
+ 	}
+ 	else
+ 	{
+ 		/* non upgrade case */
+ 		newOid = GetNewOid(pg_enum);
+ 	}
+ 
+ 	if (neighbour == NULL)
+ 	{
+ 		/* 
+ 		 * Put the new label at the end of the list.
+ 		 * No change to existing tuples is required.
+ 		 */
+ 		newelemorder = nelems + 1;
+ 		/* are the elements still sorted? */
+ 		if (elems_are_sorted)
+ 		{	
+ 			CatCList   *list;
+ 			int			i;
+ 			bool        still_sorted = true;
+ 
+ 			list = SearchSysCacheList1(ENUMTYPOIDNAME, 
+ 									   ObjectIdGetDatum(enumTypeOid));
+ 			for (i = 0; i < nelems; i++)
+ 			{
+ 				HeapTuple tup = &(list->members[i]->tuple);
+ 				Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
+ 
+ 				if (en->enumsortorder == nelems)
+ 				{
+ 					if (HeapTupleGetOid(tup) > newOid)
+ 						still_sorted = false;
+ 					break;
+ 				}
+ 			}
+ 			ReleaseCatCacheList(list);
+ 			if (! still_sorted)
+ 				result = false;
+ 		}
+ 	}
+ 	else 
+ 	{
+ 		/* BEFORE or AFTER specified */
+ 		CatCList   *list;
+ 		int			i;
+ 		HeapTuple    *existing;
+ 		HeapTuple     nbr = NULL;
+ 		Form_pg_enum nbr_en;
+ 
+ 		/* get a list of the existing elements and sort them by enumsortorder */
+ 		list = SearchSysCacheList1(ENUMTYPOIDNAME, 
+ 								   ObjectIdGetDatum(enumTypeOid));
+ 		existing = palloc(nelems * sizeof(HeapTuple));
+ 
+ 		for (i = 0; i < nelems; i++)
+ 			existing[i] = &(list->members[i]->tuple);
+ 
+ 		qsort(existing, nelems, sizeof(HeapTuple), sort_order_cmp);
+ 		
+ 		/* locate the neighbour element */
+ 		for (i = 0; i < nelems; i++)
+ 		{
+ 			Form_pg_enum exists_en;
+ 			exists_en = (Form_pg_enum) GETSTRUCT(existing[i]);
+ 			if (strcmp(NameStr(exists_en->enumlabel),neighbour) == 0)
+ 				nbr = existing[i];
+ 
+ 		}
+ 
+ 		if (nbr == NULL)
+ 		{
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("\"%s\" is not an existing label.", neighbour)));
+ 		}
+ 
+ 		nbr_en = (Form_pg_enum) GETSTRUCT(nbr);
+ 
+ 		/* 
+ 		 * If BEFORE was specified, the new label goes in the neighbour's
+ 		 * position. Otherwise, it goes in the position after that.
+ 		 */
+ 		newelemorder = nbr_en->enumsortorder;
+ 		if (newValIsAfter)
+ 			newelemorder++;
+ 
+ 		/* 
+ 		 * Add 1 to the sortorder of all the labels after where the
+ 		 * new label goes. Do it from the end back so we don't get
+ 		 * uniqueness violations.
+ 		 */
+ 		for (i = nelems - 1; i>= 0; i--)
+ 		{
+ 			HeapTuple newtup;
+ 			Form_pg_enum exst_en = (Form_pg_enum) GETSTRUCT(existing[i]);
+ 			if (exst_en->enumsortorder < newelemorder)
+ 				break;
+ 			
+ 			newtup = heap_copytuple(existing[i]);
+ 			exst_en = (Form_pg_enum) GETSTRUCT(newtup);
+ 			exst_en->enumsortorder ++;
+ 
+ 			simple_heap_update(pg_enum, &newtup->t_self, newtup);
+ 
+ 			CatalogUpdateIndexes(pg_enum, newtup);
+ 
+ 		}
+ 
+ 		ReleaseCatCacheList(list);
+ 
+ 		/* are the labels sorted by OID? */
+ 		if (result && newelemorder > 1)
+ 			result = newOid > HeapTupleGetOid(existing[newelemorder-2]);
+ 		if (result && newelemorder < nelems + 1)
+ 			result = newOid < HeapTupleGetOid(existing[newelemorder-1]);
+  
+ 	}
+ 
+ 	/* set up the new entry */
+ 	tupDesc = pg_enum->rd_att;
+ 	memset(nulls, false, sizeof(nulls));
+ 	values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
+ 	namestrcpy(&enumlabel, newVal);
+ 	values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
+ 	values[Anum_pg_enum_enumsortorder -1] =	Int32GetDatum(newelemorder);
+ 	enum_tup = heap_form_tuple(tupDesc, 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);
+ 
+ 	return result;
+ 
+ }
  
  
  /*
***************
*** 33,40 **** 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)
  {
  	Relation	pg_enum;
  	TupleDesc	tupDesc;
--- 223,229 ----
   * vals is a list of Value strings.
   */
  void
! EnumValuesCreate(Oid enumTypeOid, List *vals)
  {
  	Relation	pg_enum;
  	TupleDesc	tupDesc;
***************
*** 50,58 **** EnumValuesCreate(Oid enumTypeOid, List *vals,
  	num_elems = list_length(vals);
  
  	/*
! 	 * XXX 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?
  	 */
  
  	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
--- 239,247 ----
  	num_elems = list_length(vals);
  
  	/*
! 	 * 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.
! 	 * It is probably not worth trying harder.
  	 */
  
  	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
***************
*** 62,96 **** EnumValuesCreate(Oid enumTypeOid, List *vals,
  	 * Allocate oids
  	 */
  	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
  	{
  		/*
! 		 * 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.
  		 */
! 		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);
  	}
  
  	/* and make the entries */
  	memset(nulls, false, sizeof(nulls));
--- 251,274 ----
  	 * Allocate oids
  	 */
  	oids = (Oid *) palloc(num_elems * sizeof(Oid));
! 
! 	/*
! 	 * 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.
! 	 */
! 	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);
  
  	/* and make the entries */
  	memset(nulls, false, sizeof(nulls));
***************
*** 114,119 **** EnumValuesCreate(Oid enumTypeOid, List *vals,
--- 292,298 ----
  		values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
  		namestrcpy(&enumlabel, lab);
  		values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
+ 		values[Anum_pg_enum_enumsortorder -1] = Int32GetDatum(elemno+1);
  
  		tup = heap_form_tuple(tupDesc, values, nulls);
  		HeapTupleSetOid(tup, oids[elemno]);
***************
*** 164,170 **** EnumValuesDelete(Oid enumTypeOid)
  }
  
  
! /* qsort comparison function */
  static int
  oid_cmp(const void *p1, const void *p2)
  {
--- 343,349 ----
  }
  
  
! /* qsort comparison for oids */
  static int
  oid_cmp(const void *p1, const void *p2)
  {
***************
*** 177,179 **** oid_cmp(const void *p1, const void *p2)
--- 356,371 ----
  		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);
+ 
+ 	return en1->enumsortorder - en2->enumsortorder;
+ }
+ 
*** a/src/backend/catalog/pg_type.c
--- b/src/backend/catalog/pg_type.c
***************
*** 112,117 **** TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId)
--- 112,119 ----
  	values[i++] = ObjectIdGetDatum(InvalidOid); /* typbasetype */
  	values[i++] = Int32GetDatum(-1);	/* typtypmod */
  	values[i++] = Int32GetDatum(0);		/* typndims */
+ 	values[i++] = Int32GetDatum(-1);	/* typnlabels */
+ 	values[i++] = BoolGetDatum(false);	/* typsorted */
  	nulls[i++] = true;			/* typdefaultbin */
  	nulls[i++] = true;			/* typdefault */
  
***************
*** 204,210 **** TypeCreate(Oid newTypeOid,
  		   char storage,
  		   int32 typeMod,
  		   int32 typNDims,		/* Array dimensions for baseType */
! 		   bool typeNotNull)
  {
  	Relation	pg_type_desc;
  	Oid			typeObjectId;
--- 206,214 ----
  		   char storage,
  		   int32 typeMod,
  		   int32 typNDims,		/* Array dimensions for baseType */
! 		   bool typeNotNull,
! 		   int32 typeNLabels,
! 		   bool typeSorted)
  {
  	Relation	pg_type_desc;
  	Oid			typeObjectId;
***************
*** 342,347 **** TypeCreate(Oid newTypeOid,
--- 346,353 ----
  	values[i++] = ObjectIdGetDatum(baseType);	/* typbasetype */
  	values[i++] = Int32GetDatum(typeMod);		/* typtypmod */
  	values[i++] = Int32GetDatum(typNDims);		/* typndims */
+ 	values[i++] = Int32GetDatum(typeNLabels);	/* typnlabels */
+ 	values[i++] = BoolGetDatum(typeSorted);	    /* typsorted */
  
  	/*
  	 * initialize the default binary value for this type.  Check for nulls of
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
***************
*** 85,96 **** 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 char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
  					Oid baseTypeOid,
  					int typMod, Constraint *constr,
  					char *domainName);
- 
- 
  /*
   * DefineType
   *		Registers a new base type.
--- 85,95 ----
  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 checkEnumOwner(HeapTuple tup, TypeName *typename);
  static char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
  					Oid baseTypeOid,
  					int typMod, Constraint *constr,
  					char *domainName);
  /*
   * DefineType
   *		Registers a new base type.
***************
*** 562,569 **** DefineType(List *names, List *parameters)
  				   storage,		/* TOAST strategy */
  				   -1,			/* typMod (Domains only) */
  				   0,			/* Array Dimensions of typbasetype */
! 				   false);		/* Type NOT NULL */
! 
  	/*
  	 * Create the array type that goes with it.
  	 */
--- 561,569 ----
  				   storage,		/* TOAST strategy */
  				   -1,			/* typMod (Domains only) */
  				   0,			/* Array Dimensions of typbasetype */
! 				   false,		/* Type NOT NULL */
! 				   -1,          /* no enum labels */
! 				   false);        /* type is not an enum, so not sorted */
  	/*
  	 * Create the array type that goes with it.
  	 */
***************
*** 601,607 **** DefineType(List *names, List *parameters)
  			   'x',				/* ARRAY is always toastable */
  			   -1,				/* typMod (Domains only) */
  			   0,				/* Array dimensions of typbasetype */
! 			   false);			/* Type NOT NULL */
  
  	pfree(array_type);
  }
--- 601,609 ----
  			   'x',				/* ARRAY is always toastable */
  			   -1,				/* typMod (Domains only) */
  			   0,				/* Array dimensions of typbasetype */
! 			   false,			/* Type NOT NULL */
! 			   -1,              /* no enum labels */
! 			   false);          /* type is not an enum, so not sorted */
  
  	pfree(array_type);
  }
***************
*** 1044,1050 **** DefineDomain(CreateDomainStmt *stmt)
  				   storage,		/* TOAST strategy */
  				   basetypeMod, /* typeMod value */
  				   typNDims,	/* Array dimensions for base type */
! 				   typNotNull); /* Type NOT NULL */
  
  	/*
  	 * Process constraints which refer to the domain ID returned by TypeCreate
--- 1046,1054 ----
  				   storage,		/* TOAST strategy */
  				   basetypeMod, /* typeMod value */
  				   typNDims,	/* Array dimensions for base type */
! 				   typNotNull,  /* Type NOT NULL */
! 				   -1,          /* no enum labels */
! 				   false);      /* type is not an enum, so not sorted */
  
  	/*
  	 * Process constraints which refer to the domain ID returned by TypeCreate
***************
*** 1094,1099 **** DefineEnum(CreateEnumStmt *stmt)
--- 1098,1106 ----
  	AclResult	aclresult;
  	Oid			old_type_oid;
  	Oid			enumArrayOid;
+ 	int         num_labels;
+ 
+ 	num_labels = list_length(stmt->vals);
  
  	/* Convert list of names to a name and namespace */
  	enumNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
***************
*** 1153,1162 **** DefineEnum(CreateEnumStmt *stmt)
  				   'p',			/* TOAST strategy always plain */
  				   -1,			/* typMod (Domains only) */
  				   0,			/* Array dimensions of typbasetype */
! 				   false);		/* Type NOT NULL */
  
  	/* Enter the enum's values into pg_enum */
! 	EnumValuesCreate(enumTypeOid, stmt->vals, InvalidOid);
  
  	/*
  	 * Create the array type that goes with it.
--- 1160,1171 ----
  				   'p',			/* TOAST strategy always plain */
  				   -1,			/* typMod (Domains only) */
  				   0,			/* Array dimensions of typbasetype */
! 				   false,		/* Type NOT NULL */
! 				   num_labels,  /* count enum labels */
! 				   true);       /* enums always start sorted */
  
  	/* Enter the enum's values into pg_enum */
! 	EnumValuesCreate(enumTypeOid, stmt->vals);
  
  	/*
  	 * Create the array type that goes with it.
***************
*** 1192,1202 **** DefineEnum(CreateEnumStmt *stmt)
  			   'x',				/* ARRAY is always toastable */
  			   -1,				/* typMod (Domains only) */
  			   0,				/* Array dimensions of typbasetype */
! 			   false);			/* Type NOT NULL */
  
  	pfree(enumArrayName);
  }
  
  
  /*
   * Find suitable I/O functions for a type.
--- 1201,1288 ----
  			   'x',				/* ARRAY is always toastable */
  			   -1,				/* typMod (Domains only) */
  			   0,				/* Array dimensions of typbasetype */
! 			   false,			/* Type NOT NULL */
! 			   -1,              /* no enum labels */
! 			   false);          /* type is not an enum, so not sorted */
  
  	pfree(enumArrayName);
  }
  
+ /*
+  * AlterEnum
+  *		Registers a new label for an existing enum.
+  */
+ void
+ AlterEnum (AlterEnumStmt *stmt)
+ {
+ 	Oid			enum_type_oid;
+ 	TypeName  *typename;
+ 	bool       sorted;
+ 	HeapTuple tup, newtup;
+ 	Relation  rel;
+ 	Form_pg_type typTup;
+ 
+ 	/* Make a TypeName so we can use standard type lookup machinery */
+ 	typename = makeTypeNameFromNameList(stmt->typeName);
+ 	enum_type_oid = typenameTypeId(NULL, typename, NULL);
+ 
+ 	/* Look up the row in the type table */
+ 	rel = heap_open(TypeRelationId, RowExclusiveLock);
+ 
+ 	tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(enum_type_oid));
+ 	if (!HeapTupleIsValid(tup))
+ 		elog(ERROR, "cache lookup failed for type %u", enum_type_oid);
+ 
+ 	/* Copy the syscache entry so we can scribble on it below */
+ 	newtup = heap_copytuple(tup);
+ 	ReleaseSysCache(tup);
+ 	tup = newtup;
+ 	typTup = (Form_pg_type) GETSTRUCT(tup);
+ 
+ 	/* Check it's an enum and check user has permission to ALTER the enum */
+ 	checkEnumOwner(tup, typename);
+ 
+ 	/* Add the new label */
+ 	sorted = AddEnumLabel (enum_type_oid, stmt->newVal, 
+ 						   stmt->newValNeighbour, stmt->newValIsAfter,
+ 						   typTup->typnlabels, typTup->typsorted);
+ 
+ 	/* Update the row in pg_type */
+ 	typTup->typnlabels += 1;
+ 	typTup->typsorted = sorted;
+ 
+ 	simple_heap_update(rel, &tup->t_self, tup);
+ 
+ 	CatalogUpdateIndexes(rel, tup);
+ 
+ 	/* Clean up */
+ 	heap_close(rel, RowExclusiveLock);
+ }
+ 
+ 
+ /*
+  * 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, TypeName *typename)
+ {
+ 	Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup);
+ 
+ 	/* Check that this is actually a domain */
+ 	if (typTup->typtype != TYPTYPE_ENUM)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 				 errmsg("\"%s\" is not an enum",
+ 						TypeNameToString(typename))));
+ 
+ 	/* 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.
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2836,2841 **** _copyCreateEnumStmt(CreateEnumStmt *from)
--- 2836,2854 ----
  	return newnode;
  }
  
+ static AlterEnumStmt *
+ _copyAlterEnumStmt(AlterEnumStmt *from)
+ {
+ 	AlterEnumStmt *newnode = makeNode(AlterEnumStmt);
+ 
+ 	COPY_NODE_FIELD(typeName);
+ 	COPY_STRING_FIELD(newVal);
+ 	COPY_STRING_FIELD(newValNeighbour);
+ 	COPY_SCALAR_FIELD(newValIsAfter);
+ 
+ 	return newnode;
+ }
+ 
  static ViewStmt *
  _copyViewStmt(ViewStmt *from)
  {
***************
*** 3989,3994 **** copyObject(void *from)
--- 4002,4010 ----
  		case T_CreateEnumStmt:
  			retval = _copyCreateEnumStmt(from);
  			break;
+ 		case T_AlterEnumStmt:
+ 			retval = _copyAlterEnumStmt(from);
+ 			break;
  		case T_ViewStmt:
  			retval = _copyViewStmt(from);
  			break;
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 1375,1380 **** _equalCreateEnumStmt(CreateEnumStmt *a, CreateEnumStmt *b)
--- 1375,1391 ----
  }
  
  static bool
+ _equalAlterEnumStmt(AlterEnumStmt *a, AlterEnumStmt *b)
+ {
+ 	COMPARE_NODE_FIELD(typeName);
+ 	COMPARE_STRING_FIELD(newVal);
+ 	COMPARE_STRING_FIELD(newValNeighbour);
+ 	COMPARE_SCALAR_FIELD(newValIsAfter);
+ 
+ 	return true;
+ }
+ 
+ static bool
  _equalViewStmt(ViewStmt *a, ViewStmt *b)
  {
  	COMPARE_NODE_FIELD(view);
***************
*** 2678,2683 **** equal(void *a, void *b)
--- 2689,2697 ----
  		case T_CreateEnumStmt:
  			retval = _equalCreateEnumStmt(a, b);
  			break;
+ 		case T_AlterEnumStmt:
+ 			retval = _equalAlterEnumStmt(a, b);
+ 			break;
  		case T_ViewStmt:
  			retval = _equalViewStmt(a, b);
  			break;
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 181,188 **** static TypeName *TableFuncTypeName(List *columns);
  }
  
  %type <node>	stmt schema_stmt
! 		AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterFdwStmt
! 		AlterForeignServerStmt AlterGroupStmt
  		AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
  		AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
  		AlterRoleStmt AlterRoleSetStmt
--- 181,188 ----
  }
  
  %type <node>	stmt schema_stmt
!         AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
!         AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
  		AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
  		AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
  		AlterRoleStmt AlterRoleSetStmt
***************
*** 648,653 **** stmt :
--- 648,654 ----
  			| AlterDatabaseSetStmt
  			| AlterDefaultPrivilegesStmt
  			| AlterDomainStmt
+ 			| AlterEnumStmt
  			| AlterFdwStmt
  			| AlterForeignServerStmt
  			| AlterFunctionStmt
***************
*** 3781,3786 **** enum_val_list:	Sconst
--- 3782,3827 ----
  				{ $$ = 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->newValNeighbour = 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->newValNeighbour = $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->newValNeighbour = $7;
+ 				 n->newValIsAfter = true;
+ 
+ 				 $$ = (Node *) n;
+ 			 }
+ 		 ;
+ 
+ 
  
  /*****************************************************************************
   *
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 189,194 **** check_xact_readonly(Node *parsetree)
--- 189,195 ----
  		case T_CreateTrigStmt:
  		case T_CompositeTypeStmt:
  		case T_CreateEnumStmt:
+ 		case T_AlterEnumStmt:
  		case T_ViewStmt:
  		case T_DropCastStmt:
  		case T_DropStmt:
***************
*** 846,851 **** standard_ProcessUtility(Node *parsetree,
--- 847,856 ----
  			DefineEnum((CreateEnumStmt *) parsetree);
  			break;
  
+ 		case T_AlterEnumStmt:	/* ALTER TYPE (enum) */
+ 			AlterEnum((AlterEnumStmt *) parsetree);
+ 			break;
+ 
  		case T_ViewStmt:		/* CREATE VIEW */
  			DefineView((ViewStmt *) parsetree, queryString);
  			break;
***************
*** 1846,1851 **** CreateCommandTag(Node *parsetree)
--- 1851,1860 ----
  			tag = "CREATE TYPE";
  			break;
  
+ 		case T_AlterEnumStmt:
+ 			tag = "ALTER TYPE";
+ 			break;
+ 
  		case T_ViewStmt:
  			tag = "CREATE VIEW";
  			break;
***************
*** 2384,2389 **** GetCommandLogLevel(Node *parsetree)
--- 2393,2402 ----
  			lev = LOGSTMT_DDL;
  			break;
  
+ 		case T_AlterEnumStmt:
+ 			lev = LOGSTMT_DDL;
+ 			break;
+ 
  		case T_ViewStmt:
  			lev = LOGSTMT_DDL;
  			break;
*** a/src/backend/utils/adt/enum.c
--- b/src/backend/utils/adt/enum.c
***************
*** 14,19 ****
--- 14,20 ----
  #include "postgres.h"
  
  #include "catalog/pg_enum.h"
+ #include "catalog/pg_type.h"
  #include "fmgr.h"
  #include "utils/array.h"
  #include "utils/builtins.h"
***************
*** 22,30 ****
  #include "libpq/pqformat.h"
  #include "miscadmin.h"
  
  
  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 */
--- 23,48 ----
  #include "libpq/pqformat.h"
  #include "miscadmin.h"
  
+ typedef struct 
+ {
+ 	Oid      enum_oid;
+ 	int32   sort_order;
+ } enum_sort;
+ 
+ typedef struct 
+ {
+ 	Oid       enumtypoid;
+ 	bool      oids_are_sorted;
+ 	int       sort_list_length;
+ 	int       label_count;
+ 	enum_sort sort_order_list[1];
+ } enum_sort_cache;
+ 	
+ 
  
  static ArrayType *enum_range_internal(Oid enumtypoid, Oid lower, Oid upper);
! static int	enum_sort_cmp(const void *left, const void *right);
! static int	enum_oid_cmp(const void *left, const void *right);
  
  
  /* Basic I/O support */
***************
*** 155,167 **** enum_send(PG_FUNCTION_ARGS)
  
  /* Comparison functions and related */
  
  Datum
  enum_lt(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a < b);
  }
  
  Datum
--- 173,314 ----
  
  /* Comparison functions and related */
  
+ static inline int 
+ enum_ccmp(Oid arg1, Oid arg2, FunctionCallInfo fcinfo)
+ {
+ 
+ 	enum_sort_cache * mycache;
+ 	enum_sort *es1, *es2, srch;
+ 	int sort1, sort2;
+ 	bool added = false;
+ 	HeapTuple	enum_tup, type_tup;
+ 	Form_pg_enum en;
+ 	Oid typeoid;
+ 	Form_pg_type typ;
+ 
+ 	if (arg1 == arg2)
+ 		return 0;
+ 
+ 	mycache = (enum_sort_cache *) fcinfo->flinfo->fn_extra;
+ 	if (mycache == NULL )
+ 	{
+ 		enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg1));
+ 		en = (Form_pg_enum) GETSTRUCT(enum_tup);
+ 		typeoid = en->enumtypid;
+ 		type_tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typeoid));
+ 		typ = (Form_pg_type)  GETSTRUCT(type_tup);
+ 		if (typ->typtype != 'e')
+ 			elog(ERROR,"wrong type for oid %u",typeoid);
+         fcinfo->flinfo->fn_extra = 
+ 		  MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ 							 sizeof(enum_sort_cache) + 
+ 							 (typ->typnlabels * sizeof(enum_sort)));
+         mycache = (enum_sort_cache *) fcinfo->flinfo->fn_extra;
+ 		mycache->enumtypoid = typeoid;
+ 		mycache->sort_list_length = 1;
+ 		mycache->label_count = typ->typnlabels;
+ 		mycache->oids_are_sorted = typ->typsorted;
+ 		mycache->sort_order_list[0].enum_oid = arg1;
+ 		mycache->sort_order_list[0].sort_order = en->enumsortorder;
+ 		ReleaseSysCache(type_tup);
+ 		ReleaseSysCache(enum_tup);
+ 	}
+ 
+ 	if (mycache->oids_are_sorted)
+ 		return arg1 - arg2;
+ 
+ 	srch.enum_oid = arg1;
+ 	es1 = bsearch(&srch,
+ 				  mycache->sort_order_list,
+ 				  mycache->sort_list_length,
+ 				  sizeof(enum_sort),
+ 				  enum_oid_cmp);
+ 	srch.enum_oid = arg2;
+ 	es2 = bsearch(&srch,
+ 				  mycache->sort_order_list,
+ 				  mycache->sort_list_length,
+ 				  sizeof(enum_sort),
+ 				  enum_oid_cmp);
+ 
+ 	if (es1 == NULL)
+ 	{
+ 		
+ 		enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg1));
+ 		en = (Form_pg_enum) GETSTRUCT(enum_tup);
+ 		mycache->sort_order_list[mycache->sort_list_length].enum_oid = arg1;
+ 		sort1 = en->enumsortorder;
+ 		mycache->sort_order_list[mycache->sort_list_length].sort_order = 
+ 			sort1; 
+ 		ReleaseSysCache(enum_tup);
+ 		mycache->sort_list_length ++;
+ 		added = true;
+ 	}
+ 	else
+ 	{
+ 		/* already in cache */
+ 		sort1 = es1->sort_order;
+ 	}
+ 
+ 	if (es2 == NULL)
+ 	{
+ 		
+ 		enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg2));
+ 		en = (Form_pg_enum) GETSTRUCT(enum_tup);
+ 		sort2 = en->enumsortorder;
+ 		mycache->sort_order_list[mycache->sort_list_length].enum_oid = arg2;
+ 		mycache->sort_order_list[mycache->sort_list_length].sort_order = 
+ 			sort2;
+ 		ReleaseSysCache(enum_tup);
+ 		mycache->sort_list_length ++;
+ 		added = true;
+ 	}
+ 	else
+ 	{
+ 		/* already in cache */
+ 		sort2 = es2->sort_order;
+ 	}
+ 
+ 	if (added)
+ 	{
+ 		/* 
+ 		 * If we have more than a handful, just fetch them all, so we limit
+ 		 * the number of sort operations required.
+ 		 */
+ 		if (mycache->sort_list_length > 10 && 
+ 			mycache->sort_list_length < mycache->label_count)
+ 		{
+ 			CatCList   *nlist;
+ 			int			num, i;
+ 
+ 			nlist = SearchSysCacheList1(ENUMTYPOIDNAME, 
+ 									   ObjectIdGetDatum(mycache->enumtypoid));
+ 			num = nlist->n_members;
+ 			for (i = 0; i < num; i++)
+ 			{
+ 				HeapTuple tup = &(nlist->members[i]->tuple);
+ 				Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
+ 				
+ 				mycache->sort_order_list[i].enum_oid = HeapTupleGetOid(tup);
+ 				mycache->sort_order_list[i].sort_order = en->enumsortorder; 
+ 			}
+ 
+ 			ReleaseCatCacheList(nlist);
+ 		}
+ 
+ 		qsort(mycache->sort_order_list,mycache->sort_list_length,
+ 			  sizeof(enum_sort),enum_oid_cmp);
+ 	}
+ 
+ 	return sort1 - sort2;
+ }
+ 
  Datum
  enum_lt(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) < 0);
  }
  
  Datum
***************
*** 170,176 **** enum_le(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a <= b);
  }
  
  Datum
--- 317,323 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) <= 0);
  }
  
  Datum
***************
*** 197,203 **** enum_ge(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a >= b);
  }
  
  Datum
--- 344,350 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) >= 0);
  }
  
  Datum
***************
*** 206,212 **** enum_gt(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a > b);
  }
  
  Datum
--- 353,359 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) > 0);
  }
  
  Datum
***************
*** 215,221 **** enum_smaller(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(a <= b ? a : b);
  }
  
  Datum
--- 362,368 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(enum_ccmp(a,b,fcinfo) < 0 ? a : b);
  }
  
  Datum
***************
*** 224,230 **** enum_larger(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(a >= b ? a : b);
  }
  
  Datum
--- 371,377 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(enum_ccmp(a,b,fcinfo) > 0 ? a : b);
  }
  
  Datum
***************
*** 233,242 **** 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)
  		PG_RETURN_INT32(0);
  	else
  		PG_RETURN_INT32(-1);
  }
--- 380,389 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	if (a == b)
  		PG_RETURN_INT32(0);
+ 	else if (enum_ccmp(a,b,fcinfo) > 0)
+ 		PG_RETURN_INT32(1);
  	else
  		PG_RETURN_INT32(-1);
  }
***************
*** 248,253 **** enum_first(PG_FUNCTION_ARGS)
--- 395,401 ----
  {
  	Oid			enumtypoid;
  	Oid			min = InvalidOid;
+ 	int         min_sort = -1; /* value will never in fact be used */
  	CatCList   *list;
  	int			num,
  				i;
***************
*** 267,276 **** enum_first(PG_FUNCTION_ARGS)
  	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);
--- 415,428 ----
  	num = list->n_members;
  	for (i = 0; i < num; i++)
  	{
! 		HeapTuple tup = &(list->members[i]->tuple);
! 		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
! 
! 		if (!OidIsValid(min) || en->enumsortorder < min_sort)
! 		{
! 			min = HeapTupleGetOid(tup);
! 			min_sort = en->enumsortorder;
! 		}
  	}
  
  	ReleaseCatCacheList(list);
***************
*** 287,292 **** enum_last(PG_FUNCTION_ARGS)
--- 439,445 ----
  {
  	Oid			enumtypoid;
  	Oid			max = InvalidOid;
+ 	int         max_sort = -1; /* value will never in fact be used */
  	CatCList   *list;
  	int			num,
  				i;
***************
*** 306,315 **** enum_last(PG_FUNCTION_ARGS)
  	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);
--- 459,472 ----
  	num = list->n_members;
  	for (i = 0; i < num; i++)
  	{
! 		HeapTuple tup = &(list->members[i]->tuple);
! 		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
! 
! 		if (!OidIsValid(max) || en->enumsortorder > max_sort)
! 		{
! 			max = HeapTupleGetOid(tup);
! 			max_sort = en->enumsortorder;
! 		}
  	}
  
  	ReleaseCatCacheList(list);
***************
*** 382,427 **** enum_range_internal(Oid enumtypoid, Oid lower, Oid upper)
  				i,
  				j;
  	Datum	   *elems;
  
  	list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid));
  	total = list->n_members;
  
  	elems = (Datum *) palloc(total * sizeof(Datum));
  
- 	j = 0;
  	for (i = 0; i < total; i++)
  	{
! 		Oid			val = HeapTupleGetOid(&(list->members[i]->tuple));
  
- 		if ((!OidIsValid(lower) || lower <= val) &&
- 			(!OidIsValid(upper) || val <= upper))
- 			elems[j++] = ObjectIdGetDatum(val);
  	}
  
  	/* shouldn't need the cache anymore */
  	ReleaseCatCacheList(list);
  
! 	/* sort results into OID order */
! 	qsort(elems, j, sizeof(Datum), enum_elem_cmp);
  
  	/* note this hardwires some details about the representation of Oid */
  	result = construct_array(elems, j, 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;
  }
--- 539,614 ----
  				i,
  				j;
  	Datum	   *elems;
+ 	enum_sort  *sort_items;
+ 	bool        left_found;
  
  	list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid));
  	total = list->n_members;
  
  	elems = (Datum *) palloc(total * sizeof(Datum));
+ 	sort_items = (enum_sort *) palloc(total * sizeof(enum_sort));
  
  	for (i = 0; i < total; i++)
  	{
! 		HeapTuple tup = &(list->members[i]->tuple);
! 		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
! 
! 		sort_items[i].enum_oid = HeapTupleGetOid(tup);
! 		sort_items[i].sort_order = en->enumsortorder;
  
  	}
  
  	/* shouldn't need the cache anymore */
  	ReleaseCatCacheList(list);
  
! 	/* sort results into sort_order sequence */
! 	qsort(sort_items, total, sizeof(enum_sort), enum_sort_cmp);
! 
! 	j = 0; 
! 	left_found = !OidIsValid(lower);
! 	for (i=0; i < total; i++)
! 	{
! 		if (! left_found && lower == sort_items[i].enum_oid)
! 			left_found = true;
! 
! 		if (left_found)
! 			elems[j++] = ObjectIdGetDatum(sort_items[i].enum_oid);
! 
! 		if (OidIsValid(upper) && upper == sort_items[i].enum_oid)
! 			break;
! 	}
  
  	/* note this hardwires some details about the representation of Oid */
  	result = construct_array(elems, j, enumtypoid, sizeof(Oid), true, 'i');
  
  	pfree(elems);
+ 	pfree(sort_items);
  
  	return result;
  }
  
! /* 
!  * qsort comparison using sort order, for range routines 
!  */
  static int
! enum_sort_cmp(const void *left, const void *right)
  {
! 	enum_sort  *l = (enum_sort *) left;
! 	enum_sort  *r = (enum_sort *) right;
! 
! 	return l->sort_order - r->sort_order;
  }
+ 
+ /* 
+  * qsort comparison using OID order for comparison search cache
+  */
+ static int 
+ enum_oid_cmp(const void *es1, const void *es2)
+ {
+ 	enum_sort *p1, *p2;
+ 	p1 = (enum_sort *)es1;
+ 	p2 = (enum_sort *)es2;
+ 	return p1->enum_oid - p2->enum_oid;
+ }
+ 
+ 
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 6605,6621 **** dumpEnumType(Archive *fout, TypeInfo *tyinfo)
  	PQExpBuffer query = createPQExpBuffer();
  	PGresult   *res;
  	int			num,
! 				i;
  	Oid			enum_oid;
  	char	   *label;
  
  	/* Set proper schema search path so regproc references list correctly */
  	selectSourceSchema(tyinfo->dobj.namespace->dobj.name);
  
! 	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);
--- 6605,6628 ----
  	PQExpBuffer query = createPQExpBuffer();
  	PGresult   *res;
  	int			num,
! 		        i;
  	Oid			enum_oid;
  	char	   *label;
  
  	/* Set proper schema search path so regproc references list correctly */
  	selectSourceSchema(tyinfo->dobj.namespace->dobj.name);
  
!     if  (fout->remoteVersion > 90000)
! 		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);
***************
*** 6661,6677 **** dumpEnumType(Archive *fout, TypeInfo *tyinfo)
  		{
  			enum_oid = atooid(PQgetvalue(res, i, PQfnumber(res, "oid")));
  			label = PQgetvalue(res, i, PQfnumber(res, "enumlabel"));
! 
  			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);
! 			appendStringLiteralAH(q, label, fout);
! 			appendPQExpBuffer(q, ");\n");
  		}
- 		appendPQExpBuffer(q, "\n");
  	}
  
  	ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId,
--- 6668,6686 ----
  		{
  			enum_oid = atooid(PQgetvalue(res, i, PQfnumber(res, "oid")));
  			label = PQgetvalue(res, i, PQfnumber(res, "enumlabel"));
! 			
  			if (i == 0)
  				appendPQExpBuffer(q, "\n-- For binary upgrade, must preserve pg_enum oids\n");
  			appendPQExpBuffer(q,
! 							  "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\n");
  		}
  	}
  
  	ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId,
*** a/src/include/catalog/indexing.h
--- b/src/include/catalog/indexing.h
***************
*** 147,152 **** DECLARE_UNIQUE_INDEX(pg_enum_oid_index, 3502, on pg_enum using btree(oid oid_ops
--- 147,154 ----
  #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, 3539, on pg_enum using btree(enumtypid oid_ops, enumsortorder int4_ops));
+ #define EnumTypIdSortOrderIndexId 3539
  
  /* 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));
*** a/src/include/catalog/pg_class.h
--- b/src/include/catalog/pg_class.h
***************
*** 132,138 **** typedef FormData_pg_class *Form_pg_class;
   */
  
  /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
! DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f f r 28 0 t f f f f f 3 _null_ _null_ ));
  DESCR("");
  DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f f r 19 0 f f f f f f 3 _null_ _null_ ));
  DESCR("");
--- 132,138 ----
   */
  
  /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
! DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f f r 30 0 t f f f f f 3 _null_ _null_ ));
  DESCR("");
  DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f f r 19 0 f f f f f f 3 _null_ _null_ ));
  DESCR("");
*** a/src/include/catalog/pg_enum.h
--- b/src/include/catalog/pg_enum.h
***************
*** 35,40 **** CATALOG(pg_enum,3501)
--- 35,41 ----
  {
  	Oid			enumtypid;		/* OID of owning enum type */
  	NameData	enumlabel;		/* text representation of enum value */
+ 	int4        enumsortorder;  /* sort order for this enum label */
  } FormData_pg_enum;
  
  /* ----------------
***************
*** 48,56 **** typedef FormData_pg_enum *Form_pg_enum;
   *		compiler constants for pg_enum
   * ----------------
   */
! #define Natts_pg_enum					2
  #define Anum_pg_enum_enumtypid			1
  #define Anum_pg_enum_enumlabel			2
  
  /* ----------------
   *		pg_enum has no initial contents
--- 49,58 ----
   *		compiler constants for pg_enum
   * ----------------
   */
! #define Natts_pg_enum					3
  #define Anum_pg_enum_enumtypid			1
  #define Anum_pg_enum_enumlabel			2
+ #define Anum_pg_enum_enumsortorder		3
  
  /* ----------------
   *		pg_enum has no initial contents
***************
*** 60,67 **** 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 EnumValuesDelete(Oid enumTypeOid);
  
  #endif   /* PG_ENUM_H */
--- 62,70 ----
  /*
   * prototypes for functions in pg_enum.c
   */
! extern void EnumValuesCreate(Oid enumTypeOid, List *vals);
  extern void EnumValuesDelete(Oid enumTypeOid);
+ extern bool AddEnumLabel(Oid enumTypeOid, char *newVal, char *neighbour, 
+ 						 bool newValIsAfter, int nelems, bool elems_are_sorted);
  
  #endif   /* PG_ENUM_H */
*** a/src/include/catalog/pg_type.h
--- b/src/include/catalog/pg_type.h
***************
*** 194,199 **** CATALOG(pg_type,1247) BKI_BOOTSTRAP BKI_ROWTYPE_OID(71) BKI_SCHEMA_MACRO
--- 194,211 ----
  	 */
  	int4		typndims;
  
+     /* 
+      * typnlabels, contains a count on the number of labels an enum type has, 
+      * -1 for a non-enum type.
+      */
+     int4          typnlabels;
+ 
+     /*
+      * typsorted is true if the oids of an enum type reflect the type's sort
+      * order, false otherwise including for a non-enum type.
+      */
+     bool         typsorted;
+ 
  	/*
  	 * If typdefaultbin is not NULL, it is the nodeToString representation of
  	 * a default expression for the type.  Currently this is only used for
***************
*** 224,230 **** typedef FormData_pg_type *Form_pg_type;
   *		compiler constants for pg_type
   * ----------------
   */
! #define Natts_pg_type					28
  #define Anum_pg_type_typname			1
  #define Anum_pg_type_typnamespace		2
  #define Anum_pg_type_typowner			3
--- 236,242 ----
   *		compiler constants for pg_type
   * ----------------
   */
! #define Natts_pg_type					30
  #define Anum_pg_type_typname			1
  #define Anum_pg_type_typnamespace		2
  #define Anum_pg_type_typowner			3
***************
*** 251,258 **** typedef FormData_pg_type *Form_pg_type;
  #define Anum_pg_type_typbasetype		24
  #define Anum_pg_type_typtypmod			25
  #define Anum_pg_type_typndims			26
! #define Anum_pg_type_typdefaultbin		27
! #define Anum_pg_type_typdefault			28
  
  
  /* ----------------
--- 263,272 ----
  #define Anum_pg_type_typbasetype		24
  #define Anum_pg_type_typtypmod			25
  #define Anum_pg_type_typndims			26
! #define Anum_pg_type_typnlabels			27
! #define Anum_pg_type_typsorted			28
! #define Anum_pg_type_typdefaultbin		29
! #define Anum_pg_type_typdefault			30
  
  
  /* ----------------
***************
*** 269,351 **** typedef FormData_pg_type *Form_pg_type;
   */
  
  /* OIDS 1 - 99 */
! DATA(insert OID = 16 (	bool	   PGNSP PGUID	1 t b B t t \054 0	 0 1000 boolin boolout boolrecv boolsend - - - c p f 0 -1 0 _null_ _null_ ));
  DESCR("boolean, 'true'/'false'");
  #define BOOLOID			16
  
! DATA(insert OID = 17 (	bytea	   PGNSP PGUID -1 f b U f t \054 0	0 1001 byteain byteaout bytearecv byteasend - - - i x f 0 -1 0 _null_ _null_ ));
  DESCR("variable-length string, binary values escaped");
  #define BYTEAOID		17
  
! DATA(insert OID = 18 (	char	   PGNSP PGUID	1 t b S f t \054 0	 0 1002 charin charout charrecv charsend - - - c p f 0 -1 0 _null_ _null_ ));
  DESCR("single character");
  #define CHAROID			18
  
! DATA(insert OID = 19 (	name	   PGNSP PGUID NAMEDATALEN f b S f t \054 0 18 1003 namein nameout namerecv namesend - - - c p f 0 -1 0 _null_ _null_ ));
  DESCR("63-character type for storing system identifiers");
  #define NAMEOID			19
  
! DATA(insert OID = 20 (	int8	   PGNSP PGUID	8 FLOAT8PASSBYVAL b N f t \054 0	 0 1016 int8in int8out int8recv int8send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("~18 digit integer, 8-byte storage");
  #define INT8OID			20
  
! DATA(insert OID = 21 (	int2	   PGNSP PGUID	2 t b N f t \054 0	 0 1005 int2in int2out int2recv int2send - - - s p f 0 -1 0 _null_ _null_ ));
  DESCR("-32 thousand to 32 thousand, 2-byte storage");
  #define INT2OID			21
  
! DATA(insert OID = 22 (	int2vector PGNSP PGUID -1 f b A f t \054 0	21 1006 int2vectorin int2vectorout int2vectorrecv int2vectorsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("array of int2, used in system tables");
  #define INT2VECTOROID	22
  
! DATA(insert OID = 23 (	int4	   PGNSP PGUID	4 t b N f t \054 0	 0 1007 int4in int4out int4recv int4send - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("-2 billion to 2 billion integer, 4-byte storage");
  #define INT4OID			23
  
! DATA(insert OID = 24 (	regproc    PGNSP PGUID	4 t b N f t \054 0	 0 1008 regprocin regprocout regprocrecv regprocsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered procedure");
  #define REGPROCOID		24
  
! DATA(insert OID = 25 (	text	   PGNSP PGUID -1 f b S t t \054 0	0 1009 textin textout textrecv textsend - - - i x f 0 -1 0 _null_ _null_ ));
  DESCR("variable-length string, no limit specified");
  #define TEXTOID			25
  
! DATA(insert OID = 26 (	oid		   PGNSP PGUID	4 t b N t t \054 0	 0 1028 oidin oidout oidrecv oidsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("object identifier(oid), maximum 4 billion");
  #define OIDOID			26
  
! DATA(insert OID = 27 (	tid		   PGNSP PGUID	6 f b U f t \054 0	 0 1010 tidin tidout tidrecv tidsend - - - s p f 0 -1 0 _null_ _null_ ));
  DESCR("(block, offset), physical location of tuple");
  #define TIDOID		27
  
! DATA(insert OID = 28 (	xid		   PGNSP PGUID	4 t b U f t \054 0	 0 1011 xidin xidout xidrecv xidsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("transaction id");
  #define XIDOID 28
  
! DATA(insert OID = 29 (	cid		   PGNSP PGUID	4 t b U f t \054 0	 0 1012 cidin cidout cidrecv cidsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("command identifier type, sequence in transaction id");
  #define CIDOID 29
  
! DATA(insert OID = 30 (	oidvector  PGNSP PGUID -1 f b A f t \054 0	26 1013 oidvectorin oidvectorout oidvectorrecv oidvectorsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("array of oids, used in system tables");
  #define OIDVECTOROID	30
  
  /* hand-built rowtype entries for bootstrapped catalogs */
  /* NB: OIDs assigned here must match the BKI_ROWTYPE_OID declarations */
  
! DATA(insert OID = 71 (	pg_type			PGNSP PGUID -1 f c C f t \054 1247 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 75 (	pg_attribute	PGNSP PGUID -1 f c C f t \054 1249 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 81 (	pg_proc			PGNSP PGUID -1 f c C f t \054 1255 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 83 (	pg_class		PGNSP PGUID -1 f c C f t \054 1259 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 100 - 199 */
! DATA(insert OID = 142 ( xml		   PGNSP PGUID -1 f b U f t \054 0 0 143 xml_in xml_out xml_recv xml_send - - - i x f 0 -1 0 _null_ _null_ ));
  DESCR("XML content");
  #define XMLOID 142
! DATA(insert OID = 143 ( _xml	   PGNSP PGUID -1 f b A f t \054 0 142 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 200 - 299 */
  
! DATA(insert OID = 210 (  smgr	   PGNSP PGUID 2 t b U f t \054 0 0 0 smgrin smgrout - - - - - s p f 0 -1 0 _null_ _null_ ));
  DESCR("storage manager");
  
  /* OIDS 300 - 399 */
--- 283,365 ----
   */
  
  /* OIDS 1 - 99 */
! DATA(insert OID = 16 (	bool	   PGNSP PGUID	1 t b B t t \054 0	 0 1000 boolin boolout boolrecv boolsend - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("boolean, 'true'/'false'");
  #define BOOLOID			16
  
! DATA(insert OID = 17 (	bytea	   PGNSP PGUID -1 f b U f t \054 0	0 1001 byteain byteaout bytearecv byteasend - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("variable-length string, binary values escaped");
  #define BYTEAOID		17
  
! DATA(insert OID = 18 (	char	   PGNSP PGUID	1 t b S f t \054 0	 0 1002 charin charout charrecv charsend - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("single character");
  #define CHAROID			18
  
! DATA(insert OID = 19 (	name	   PGNSP PGUID NAMEDATALEN f b S f t \054 0 18 1003 namein nameout namerecv namesend - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("63-character type for storing system identifiers");
  #define NAMEOID			19
  
! DATA(insert OID = 20 (	int8	   PGNSP PGUID	8 FLOAT8PASSBYVAL b N f t \054 0	 0 1016 int8in int8out int8recv int8send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("~18 digit integer, 8-byte storage");
  #define INT8OID			20
  
! DATA(insert OID = 21 (	int2	   PGNSP PGUID	2 t b N f t \054 0	 0 1005 int2in int2out int2recv int2send - - - s p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("-32 thousand to 32 thousand, 2-byte storage");
  #define INT2OID			21
  
! DATA(insert OID = 22 (	int2vector PGNSP PGUID -1 f b A f t \054 0	21 1006 int2vectorin int2vectorout int2vectorrecv int2vectorsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("array of int2, used in system tables");
  #define INT2VECTOROID	22
  
! DATA(insert OID = 23 (	int4	   PGNSP PGUID	4 t b N f t \054 0	 0 1007 int4in int4out int4recv int4send - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("-2 billion to 2 billion integer, 4-byte storage");
  #define INT4OID			23
  
! DATA(insert OID = 24 (	regproc    PGNSP PGUID	4 t b N f t \054 0	 0 1008 regprocin regprocout regprocrecv regprocsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered procedure");
  #define REGPROCOID		24
  
! DATA(insert OID = 25 (	text	   PGNSP PGUID -1 f b S t t \054 0	0 1009 textin textout textrecv textsend - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("variable-length string, no limit specified");
  #define TEXTOID			25
  
! DATA(insert OID = 26 (	oid		   PGNSP PGUID	4 t b N t t \054 0	 0 1028 oidin oidout oidrecv oidsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("object identifier(oid), maximum 4 billion");
  #define OIDOID			26
  
! DATA(insert OID = 27 (	tid		   PGNSP PGUID	6 f b U f t \054 0	 0 1010 tidin tidout tidrecv tidsend - - - s p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("(block, offset), physical location of tuple");
  #define TIDOID		27
  
! DATA(insert OID = 28 (	xid		   PGNSP PGUID	4 t b U f t \054 0	 0 1011 xidin xidout xidrecv xidsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("transaction id");
  #define XIDOID 28
  
! DATA(insert OID = 29 (	cid		   PGNSP PGUID	4 t b U f t \054 0	 0 1012 cidin cidout cidrecv cidsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("command identifier type, sequence in transaction id");
  #define CIDOID 29
  
! DATA(insert OID = 30 (	oidvector  PGNSP PGUID -1 f b A f t \054 0	26 1013 oidvectorin oidvectorout oidvectorrecv oidvectorsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("array of oids, used in system tables");
  #define OIDVECTOROID	30
  
  /* hand-built rowtype entries for bootstrapped catalogs */
  /* NB: OIDs assigned here must match the BKI_ROWTYPE_OID declarations */
  
! DATA(insert OID = 71 (	pg_type			PGNSP PGUID -1 f c C f t \054 1247 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 75 (	pg_attribute	PGNSP PGUID -1 f c C f t \054 1249 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 81 (	pg_proc			PGNSP PGUID -1 f c C f t \054 1255 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 83 (	pg_class		PGNSP PGUID -1 f c C f t \054 1259 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 100 - 199 */
! DATA(insert OID = 142 ( xml		   PGNSP PGUID -1 f b U f t \054 0 0 143 xml_in xml_out xml_recv xml_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("XML content");
  #define XMLOID 142
! DATA(insert OID = 143 ( _xml	   PGNSP PGUID -1 f b A f t \054 0 142 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 200 - 299 */
  
! DATA(insert OID = 210 (  smgr	   PGNSP PGUID 2 t b U f t \054 0 0 0 smgrin smgrout - - - - - s p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("storage manager");
  
  /* OIDS 300 - 399 */
***************
*** 355,585 **** DESCR("storage manager");
  /* OIDS 500 - 599 */
  
  /* OIDS 600 - 699 */
! DATA(insert OID = 600 (  point	   PGNSP PGUID 16 f b G f t \054 0 701 1017 point_in point_out point_recv point_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("geometric point '(x, y)'");
  #define POINTOID		600
! DATA(insert OID = 601 (  lseg	   PGNSP PGUID 32 f b G f t \054 0 600 1018 lseg_in lseg_out lseg_recv lseg_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("geometric line segment '(pt1,pt2)'");
  #define LSEGOID			601
! DATA(insert OID = 602 (  path	   PGNSP PGUID -1 f b G f t \054 0 0 1019 path_in path_out path_recv path_send - - - d x f 0 -1 0 _null_ _null_ ));
  DESCR("geometric path '(pt1,...)'");
  #define PATHOID			602
! DATA(insert OID = 603 (  box	   PGNSP PGUID 32 f b G f t \073 0 600 1020 box_in box_out box_recv box_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("geometric box '(lower left,upper right)'");
  #define BOXOID			603
! DATA(insert OID = 604 (  polygon   PGNSP PGUID -1 f b G f t \054 0	 0 1027 poly_in poly_out poly_recv poly_send - - - d x f 0 -1 0 _null_ _null_ ));
  DESCR("geometric polygon '(pt1,...)'");
  #define POLYGONOID		604
  
! DATA(insert OID = 628 (  line	   PGNSP PGUID 32 f b G f t \054 0 701 629 line_in line_out line_recv line_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("geometric line (not implemented)");
  #define LINEOID			628
! DATA(insert OID = 629 (  _line	   PGNSP PGUID	-1 f b A f t \054 0 628 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
  DESCR("");
  
  /* OIDS 700 - 799 */
  
! DATA(insert OID = 700 (  float4    PGNSP PGUID	4 FLOAT4PASSBYVAL b N f t \054 0	 0 1021 float4in float4out float4recv float4send - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("single-precision floating point number, 4-byte storage");
  #define FLOAT4OID 700
! DATA(insert OID = 701 (  float8    PGNSP PGUID	8 FLOAT8PASSBYVAL b N t t \054 0	 0 1022 float8in float8out float8recv float8send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("double-precision floating point number, 8-byte storage");
  #define FLOAT8OID 701
! DATA(insert OID = 702 (  abstime   PGNSP PGUID	4 t b D f t \054 0	 0 1023 abstimein abstimeout abstimerecv abstimesend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("absolute, limited-range date and time (Unix system time)");
  #define ABSTIMEOID		702
! DATA(insert OID = 703 (  reltime   PGNSP PGUID	4 t b T f t \054 0	 0 1024 reltimein reltimeout reltimerecv reltimesend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("relative, limited-range time interval (Unix delta time)");
  #define RELTIMEOID		703
! DATA(insert OID = 704 (  tinterval PGNSP PGUID 12 f b T f t \054 0	 0 1025 tintervalin tintervalout tintervalrecv tintervalsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("(abstime,abstime), time interval");
  #define TINTERVALOID	704
! DATA(insert OID = 705 (  unknown   PGNSP PGUID -2 f b X f t \054 0	 0 0 unknownin unknownout unknownrecv unknownsend - - - c p f 0 -1 0 _null_ _null_ ));
  DESCR("");
  #define UNKNOWNOID		705
  
! DATA(insert OID = 718 (  circle    PGNSP PGUID	24 f b G f t \054 0 0 719 circle_in circle_out circle_recv circle_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("geometric circle '(center,radius)'");
  #define CIRCLEOID		718
! DATA(insert OID = 719 (  _circle   PGNSP PGUID	-1 f b A f t \054 0  718 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 790 (  money	   PGNSP PGUID	 8 FLOAT8PASSBYVAL b N f t \054 0 0 791 cash_in cash_out cash_recv cash_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("monetary amounts, $d,ddd.cc");
  #define CASHOID 790
! DATA(insert OID = 791 (  _money    PGNSP PGUID	-1 f b A f t \054 0  790 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 800 - 899 */
! DATA(insert OID = 829 ( macaddr    PGNSP PGUID	6 f b U f t \054 0 0 1040 macaddr_in macaddr_out macaddr_recv macaddr_send - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("XX:XX:XX:XX:XX:XX, MAC address");
  #define MACADDROID 829
! DATA(insert OID = 869 ( inet	   PGNSP PGUID	-1 f b I t t \054 0 0 1041 inet_in inet_out inet_recv inet_send - - - i m f 0 -1 0 _null_ _null_ ));
  DESCR("IP address/netmask, host address, netmask optional");
  #define INETOID 869
! DATA(insert OID = 650 ( cidr	   PGNSP PGUID	-1 f b I f t \054 0 0 651 cidr_in cidr_out cidr_recv cidr_send - - - i m f 0 -1 0 _null_ _null_ ));
  DESCR("network IP address/netmask, network address");
  #define CIDROID 650
  
  /* OIDS 900 - 999 */
  
  /* OIDS 1000 - 1099 */
! DATA(insert OID = 1000 (  _bool		 PGNSP PGUID -1 f b A f t \054 0	16 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1001 (  _bytea	 PGNSP PGUID -1 f b A f t \054 0	17 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1002 (  _char		 PGNSP PGUID -1 f b A f t \054 0	18 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1003 (  _name		 PGNSP PGUID -1 f b A f t \054 0	19 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1005 (  _int2		 PGNSP PGUID -1 f b A f t \054 0	21 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1006 (  _int2vector PGNSP PGUID -1 f b A f t \054 0	22 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1007 (  _int4		 PGNSP PGUID -1 f b A f t \054 0	23 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  #define INT4ARRAYOID		1007
! DATA(insert OID = 1008 (  _regproc	 PGNSP PGUID -1 f b A f t \054 0	24 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1009 (  _text		 PGNSP PGUID -1 f b A f t \054 0	25 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  #define TEXTARRAYOID		1009
! DATA(insert OID = 1028 (  _oid		 PGNSP PGUID -1 f b A f t \054 0	26 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1010 (  _tid		 PGNSP PGUID -1 f b A f t \054 0	27 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1011 (  _xid		 PGNSP PGUID -1 f b A f t \054 0	28 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1012 (  _cid		 PGNSP PGUID -1 f b A f t \054 0	29 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1013 (  _oidvector PGNSP PGUID -1 f b A f t \054 0	30 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1014 (  _bpchar	 PGNSP PGUID -1 f b A f t \054 0 1042 0 array_in array_out array_recv array_send bpchartypmodin bpchartypmodout - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1015 (  _varchar	 PGNSP PGUID -1 f b A f t \054 0 1043 0 array_in array_out array_recv array_send varchartypmodin varchartypmodout - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1016 (  _int8		 PGNSP PGUID -1 f b A f t \054 0	20 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1017 (  _point	 PGNSP PGUID -1 f b A f t \054 0 600 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1018 (  _lseg		 PGNSP PGUID -1 f b A f t \054 0 601 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1019 (  _path		 PGNSP PGUID -1 f b A f t \054 0 602 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1020 (  _box		 PGNSP PGUID -1 f b A f t \073 0 603 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1021 (  _float4	 PGNSP PGUID -1 f b A f t \054 0 700 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  #define FLOAT4ARRAYOID 1021
! DATA(insert OID = 1022 (  _float8	 PGNSP PGUID -1 f b A f t \054 0 701 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1023 (  _abstime	 PGNSP PGUID -1 f b A f t \054 0 702 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1024 (  _reltime	 PGNSP PGUID -1 f b A f t \054 0 703 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1025 (  _tinterval PGNSP PGUID -1 f b A f t \054 0 704 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1027 (  _polygon	 PGNSP PGUID -1 f b A f t \054 0 604 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1033 (  aclitem	 PGNSP PGUID 12 f b U f t \054 0 0 1034 aclitemin aclitemout - - - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("access control list");
  #define ACLITEMOID		1033
! DATA(insert OID = 1034 (  _aclitem	 PGNSP PGUID -1 f b A f t \054 0 1033 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1040 (  _macaddr	 PGNSP PGUID -1 f b A f t \054 0  829 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1041 (  _inet		 PGNSP PGUID -1 f b A f t \054 0  869 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 651  (  _cidr		 PGNSP PGUID -1 f b A f t \054 0  650 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1263 (  _cstring	 PGNSP PGUID -1 f b A f t \054 0 2275 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  #define CSTRINGARRAYOID		1263
  
! DATA(insert OID = 1042 ( bpchar		 PGNSP PGUID -1 f b S f t \054 0	0 1014 bpcharin bpcharout bpcharrecv bpcharsend bpchartypmodin bpchartypmodout - i x f 0 -1 0 _null_ _null_ ));
  DESCR("char(length), blank-padded string, fixed storage length");
  #define BPCHAROID		1042
! DATA(insert OID = 1043 ( varchar	 PGNSP PGUID -1 f b S f t \054 0	0 1015 varcharin varcharout varcharrecv varcharsend varchartypmodin varchartypmodout - i x f 0 -1 0 _null_ _null_ ));
  DESCR("varchar(length), non-blank-padded string, variable storage length");
  #define VARCHAROID		1043
  
! DATA(insert OID = 1082 ( date		 PGNSP PGUID	4 t b D f t \054 0	0 1182 date_in date_out date_recv date_send - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("date");
  #define DATEOID			1082
! DATA(insert OID = 1083 ( time		 PGNSP PGUID	8 FLOAT8PASSBYVAL b D f t \054 0	0 1183 time_in time_out time_recv time_send timetypmodin timetypmodout - d p f 0 -1 0 _null_ _null_ ));
  DESCR("time of day");
  #define TIMEOID			1083
  
  /* OIDS 1100 - 1199 */
! DATA(insert OID = 1114 ( timestamp	 PGNSP PGUID	8 FLOAT8PASSBYVAL b D f t \054 0	0 1115 timestamp_in timestamp_out timestamp_recv timestamp_send timestamptypmodin timestamptypmodout - d p f 0 -1 0 _null_ _null_ ));
  DESCR("date and time");
  #define TIMESTAMPOID	1114
! DATA(insert OID = 1115 ( _timestamp  PGNSP PGUID	-1 f b A f t \054 0 1114 0 array_in array_out array_recv array_send timestamptypmodin timestamptypmodout - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1182 ( _date		 PGNSP PGUID	-1 f b A f t \054 0 1082 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1183 ( _time		 PGNSP PGUID	-1 f b A f t \054 0 1083 0 array_in array_out array_recv array_send timetypmodin timetypmodout - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1184 ( timestamptz PGNSP PGUID	8 FLOAT8PASSBYVAL b D t t \054 0	0 1185 timestamptz_in timestamptz_out timestamptz_recv timestamptz_send timestamptztypmodin timestamptztypmodout - d p f 0 -1 0 _null_ _null_ ));
  DESCR("date and time with time zone");
  #define TIMESTAMPTZOID	1184
! DATA(insert OID = 1185 ( _timestamptz PGNSP PGUID -1 f b A f t \054 0	1184 0 array_in array_out array_recv array_send timestamptztypmodin timestamptztypmodout - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1186 ( interval	 PGNSP PGUID 16 f b T t t \054 0	0 1187 interval_in interval_out interval_recv interval_send intervaltypmodin intervaltypmodout - d p f 0 -1 0 _null_ _null_ ));
  DESCR("@ <number> <units>, time interval");
  #define INTERVALOID		1186
! DATA(insert OID = 1187 ( _interval	 PGNSP PGUID	-1 f b A f t \054 0 1186 0 array_in array_out array_recv array_send intervaltypmodin intervaltypmodout - d x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 1200 - 1299 */
! DATA(insert OID = 1231 (  _numeric	 PGNSP PGUID -1 f b A f t \054 0	1700 0 array_in array_out array_recv array_send numerictypmodin numerictypmodout - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1266 ( timetz		 PGNSP PGUID 12 f b D f t \054 0	0 1270 timetz_in timetz_out timetz_recv timetz_send timetztypmodin timetztypmodout - d p f 0 -1 0 _null_ _null_ ));
  DESCR("time of day with time zone");
  #define TIMETZOID		1266
! DATA(insert OID = 1270 ( _timetz	 PGNSP PGUID -1 f b A f t \054 0	1266 0 array_in array_out array_recv array_send timetztypmodin timetztypmodout - d x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 1500 - 1599 */
! DATA(insert OID = 1560 ( bit		 PGNSP PGUID -1 f b V f t \054 0	0 1561 bit_in bit_out bit_recv bit_send bittypmodin bittypmodout - i x f 0 -1 0 _null_ _null_ ));
  DESCR("fixed-length bit string");
  #define BITOID	 1560
! DATA(insert OID = 1561 ( _bit		 PGNSP PGUID -1 f b A f t \054 0	1560 0 array_in array_out array_recv array_send bittypmodin bittypmodout - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1562 ( varbit		 PGNSP PGUID -1 f b V t t \054 0	0 1563 varbit_in varbit_out varbit_recv varbit_send varbittypmodin varbittypmodout - i x f 0 -1 0 _null_ _null_ ));
  DESCR("variable-length bit string");
  #define VARBITOID	  1562
! DATA(insert OID = 1563 ( _varbit	 PGNSP PGUID -1 f b A f t \054 0	1562 0 array_in array_out array_recv array_send varbittypmodin varbittypmodout - i x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 1600 - 1699 */
  
  /* OIDS 1700 - 1799 */
! DATA(insert OID = 1700 ( numeric	   PGNSP PGUID -1 f b N f t \054 0	0 1231 numeric_in numeric_out numeric_recv numeric_send numerictypmodin numerictypmodout - i m f 0 -1 0 _null_ _null_ ));
  DESCR("numeric(precision, decimal), arbitrary precision number");
  #define NUMERICOID		1700
  
! DATA(insert OID = 1790 ( refcursor	   PGNSP PGUID -1 f b U f t \054 0	0 2201 textin textout textrecv textsend - - - i x f 0 -1 0 _null_ _null_ ));
  DESCR("reference to cursor (portal name)");
  #define REFCURSOROID	1790
  
  /* OIDS 2200 - 2299 */
! DATA(insert OID = 2201 ( _refcursor    PGNSP PGUID -1 f b A f t \054 0 1790 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  
! DATA(insert OID = 2202 ( regprocedure  PGNSP PGUID	4 t b N f t \054 0	 0 2207 regprocedurein regprocedureout regprocedurerecv regproceduresend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered procedure (with args)");
  #define REGPROCEDUREOID 2202
  
! DATA(insert OID = 2203 ( regoper	   PGNSP PGUID	4 t b N f t \054 0	 0 2208 regoperin regoperout regoperrecv regopersend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered operator");
  #define REGOPEROID		2203
  
! DATA(insert OID = 2204 ( regoperator   PGNSP PGUID	4 t b N f t \054 0	 0 2209 regoperatorin regoperatorout regoperatorrecv regoperatorsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered operator (with args)");
  #define REGOPERATOROID	2204
  
! DATA(insert OID = 2205 ( regclass	   PGNSP PGUID	4 t b N f t \054 0	 0 2210 regclassin regclassout regclassrecv regclasssend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered class");
  #define REGCLASSOID		2205
  
! DATA(insert OID = 2206 ( regtype	   PGNSP PGUID	4 t b N f t \054 0	 0 2211 regtypein regtypeout regtyperecv regtypesend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered type");
  #define REGTYPEOID		2206
  
! DATA(insert OID = 2207 ( _regprocedure PGNSP PGUID -1 f b A f t \054 0 2202 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 2208 ( _regoper	   PGNSP PGUID -1 f b A f t \054 0 2203 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 2209 ( _regoperator  PGNSP PGUID -1 f b A f t \054 0 2204 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 2210 ( _regclass	   PGNSP PGUID -1 f b A f t \054 0 2205 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 2211 ( _regtype	   PGNSP PGUID -1 f b A f t \054 0 2206 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  #define REGTYPEARRAYOID 2211
  
  /* uuid */
! DATA(insert OID = 2950 ( uuid			PGNSP PGUID 16 f b U f t \054 0 0 2951 uuid_in uuid_out uuid_recv uuid_send - - - c p f 0 -1 0 _null_ _null_ ));
  DESCR("UUID datatype");
! DATA(insert OID = 2951 ( _uuid			PGNSP PGUID -1 f b A f t \054 0 2950 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  
  /* text search */
! DATA(insert OID = 3614 ( tsvector		PGNSP PGUID -1 f b U f t \054 0 0 3643 tsvectorin tsvectorout tsvectorrecv tsvectorsend - - ts_typanalyze i x f 0 -1 0 _null_ _null_ ));
  DESCR("text representation for text search");
  #define TSVECTOROID		3614
! DATA(insert OID = 3642 ( gtsvector		PGNSP PGUID -1 f b U f t \054 0 0 3644 gtsvectorin gtsvectorout - - - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("GiST index internal text representation for text search");
  #define GTSVECTOROID	3642
! DATA(insert OID = 3615 ( tsquery		PGNSP PGUID -1 f b U f t \054 0 0 3645 tsqueryin tsqueryout tsqueryrecv tsquerysend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("query representation for text search");
  #define TSQUERYOID		3615
! DATA(insert OID = 3734 ( regconfig		PGNSP PGUID 4 t b N f t \054 0 0 3735 regconfigin regconfigout regconfigrecv regconfigsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered text search configuration");
  #define REGCONFIGOID	3734
! DATA(insert OID = 3769 ( regdictionary	PGNSP PGUID 4 t b N f t \054 0 0 3770 regdictionaryin regdictionaryout regdictionaryrecv regdictionarysend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered text search dictionary");
  #define REGDICTIONARYOID	3769
  
! DATA(insert OID = 3643 ( _tsvector		PGNSP PGUID -1 f b A f t \054 0 3614 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 3644 ( _gtsvector		PGNSP PGUID -1 f b A f t \054 0 3642 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 3645 ( _tsquery		PGNSP PGUID -1 f b A f t \054 0 3615 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 3735 ( _regconfig		PGNSP PGUID -1 f b A f t \054 0 3734 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 3770 ( _regdictionary PGNSP PGUID -1 f b A f t \054 0 3769 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  
! DATA(insert OID = 2970 ( txid_snapshot	PGNSP PGUID -1 f b U f t \054 0 0 2949 txid_snapshot_in txid_snapshot_out txid_snapshot_recv txid_snapshot_send - - - d x f 0 -1 0 _null_ _null_ ));
  DESCR("txid snapshot");
! DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
  
  /*
   * pseudo-types
--- 369,599 ----
  /* OIDS 500 - 599 */
  
  /* OIDS 600 - 699 */
! DATA(insert OID = 600 (  point	   PGNSP PGUID 16 f b G f t \054 0 701 1017 point_in point_out point_recv point_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric point '(x, y)'");
  #define POINTOID		600
! DATA(insert OID = 601 (  lseg	   PGNSP PGUID 32 f b G f t \054 0 600 1018 lseg_in lseg_out lseg_recv lseg_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric line segment '(pt1,pt2)'");
  #define LSEGOID			601
! DATA(insert OID = 602 (  path	   PGNSP PGUID -1 f b G f t \054 0 0 1019 path_in path_out path_recv path_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric path '(pt1,...)'");
  #define PATHOID			602
! DATA(insert OID = 603 (  box	   PGNSP PGUID 32 f b G f t \073 0 600 1020 box_in box_out box_recv box_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric box '(lower left,upper right)'");
  #define BOXOID			603
! DATA(insert OID = 604 (  polygon   PGNSP PGUID -1 f b G f t \054 0	 0 1027 poly_in poly_out poly_recv poly_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric polygon '(pt1,...)'");
  #define POLYGONOID		604
  
! DATA(insert OID = 628 (  line	   PGNSP PGUID 32 f b G f t \054 0 701 629 line_in line_out line_recv line_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric line (not implemented)");
  #define LINEOID			628
! DATA(insert OID = 629 (  _line	   PGNSP PGUID	-1 f b A f t \054 0 628 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("");
  
  /* OIDS 700 - 799 */
  
! DATA(insert OID = 700 (  float4    PGNSP PGUID	4 FLOAT4PASSBYVAL b N f t \054 0	 0 1021 float4in float4out float4recv float4send - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("single-precision floating point number, 4-byte storage");
  #define FLOAT4OID 700
! DATA(insert OID = 701 (  float8    PGNSP PGUID	8 FLOAT8PASSBYVAL b N t t \054 0	 0 1022 float8in float8out float8recv float8send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("double-precision floating point number, 8-byte storage");
  #define FLOAT8OID 701
! DATA(insert OID = 702 (  abstime   PGNSP PGUID	4 t b D f t \054 0	 0 1023 abstimein abstimeout abstimerecv abstimesend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("absolute, limited-range date and time (Unix system time)");
  #define ABSTIMEOID		702
! DATA(insert OID = 703 (  reltime   PGNSP PGUID	4 t b T f t \054 0	 0 1024 reltimein reltimeout reltimerecv reltimesend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("relative, limited-range time interval (Unix delta time)");
  #define RELTIMEOID		703
! DATA(insert OID = 704 (  tinterval PGNSP PGUID 12 f b T f t \054 0	 0 1025 tintervalin tintervalout tintervalrecv tintervalsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("(abstime,abstime), time interval");
  #define TINTERVALOID	704
! DATA(insert OID = 705 (  unknown   PGNSP PGUID -2 f b X f t \054 0	 0 0 unknownin unknownout unknownrecv unknownsend - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("");
  #define UNKNOWNOID		705
  
! DATA(insert OID = 718 (  circle    PGNSP PGUID	24 f b G f t \054 0 0 719 circle_in circle_out circle_recv circle_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric circle '(center,radius)'");
  #define CIRCLEOID		718
! DATA(insert OID = 719 (  _circle   PGNSP PGUID	-1 f b A f t \054 0  718 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 790 (  money	   PGNSP PGUID	 8 FLOAT8PASSBYVAL b N f t \054 0 0 791 cash_in cash_out cash_recv cash_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("monetary amounts, $d,ddd.cc");
  #define CASHOID 790
! DATA(insert OID = 791 (  _money    PGNSP PGUID	-1 f b A f t \054 0  790 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 800 - 899 */
! DATA(insert OID = 829 ( macaddr    PGNSP PGUID	6 f b U f t \054 0 0 1040 macaddr_in macaddr_out macaddr_recv macaddr_send - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("XX:XX:XX:XX:XX:XX, MAC address");
  #define MACADDROID 829
! DATA(insert OID = 869 ( inet	   PGNSP PGUID	-1 f b I t t \054 0 0 1041 inet_in inet_out inet_recv inet_send - - - i m f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("IP address/netmask, host address, netmask optional");
  #define INETOID 869
! DATA(insert OID = 650 ( cidr	   PGNSP PGUID	-1 f b I f t \054 0 0 651 cidr_in cidr_out cidr_recv cidr_send - - - i m f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("network IP address/netmask, network address");
  #define CIDROID 650
  
  /* OIDS 900 - 999 */
  
  /* OIDS 1000 - 1099 */
! DATA(insert OID = 1000 (  _bool		 PGNSP PGUID -1 f b A f t \054 0	16 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1001 (  _bytea	 PGNSP PGUID -1 f b A f t \054 0	17 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1002 (  _char		 PGNSP PGUID -1 f b A f t \054 0	18 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1003 (  _name		 PGNSP PGUID -1 f b A f t \054 0	19 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1005 (  _int2		 PGNSP PGUID -1 f b A f t \054 0	21 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1006 (  _int2vector PGNSP PGUID -1 f b A f t \054 0	22 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1007 (  _int4		 PGNSP PGUID -1 f b A f t \054 0	23 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  #define INT4ARRAYOID		1007
! DATA(insert OID = 1008 (  _regproc	 PGNSP PGUID -1 f b A f t \054 0	24 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1009 (  _text		 PGNSP PGUID -1 f b A f t \054 0	25 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  #define TEXTARRAYOID		1009
! DATA(insert OID = 1028 (  _oid		 PGNSP PGUID -1 f b A f t \054 0	26 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1010 (  _tid		 PGNSP PGUID -1 f b A f t \054 0	27 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1011 (  _xid		 PGNSP PGUID -1 f b A f t \054 0	28 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1012 (  _cid		 PGNSP PGUID -1 f b A f t \054 0	29 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1013 (  _oidvector PGNSP PGUID -1 f b A f t \054 0	30 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1014 (  _bpchar	 PGNSP PGUID -1 f b A f t \054 0 1042 0 array_in array_out array_recv array_send bpchartypmodin bpchartypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1015 (  _varchar	 PGNSP PGUID -1 f b A f t \054 0 1043 0 array_in array_out array_recv array_send varchartypmodin varchartypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1016 (  _int8		 PGNSP PGUID -1 f b A f t \054 0	20 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1017 (  _point	 PGNSP PGUID -1 f b A f t \054 0 600 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1018 (  _lseg		 PGNSP PGUID -1 f b A f t \054 0 601 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1019 (  _path		 PGNSP PGUID -1 f b A f t \054 0 602 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1020 (  _box		 PGNSP PGUID -1 f b A f t \073 0 603 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1021 (  _float4	 PGNSP PGUID -1 f b A f t \054 0 700 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  #define FLOAT4ARRAYOID 1021
! DATA(insert OID = 1022 (  _float8	 PGNSP PGUID -1 f b A f t \054 0 701 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1023 (  _abstime	 PGNSP PGUID -1 f b A f t \054 0 702 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1024 (  _reltime	 PGNSP PGUID -1 f b A f t \054 0 703 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1025 (  _tinterval PGNSP PGUID -1 f b A f t \054 0 704 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1027 (  _polygon	 PGNSP PGUID -1 f b A f t \054 0 604 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1033 (  aclitem	 PGNSP PGUID 12 f b U f t \054 0 0 1034 aclitemin aclitemout - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("access control list");
  #define ACLITEMOID		1033
! DATA(insert OID = 1034 (  _aclitem	 PGNSP PGUID -1 f b A f t \054 0 1033 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1040 (  _macaddr	 PGNSP PGUID -1 f b A f t \054 0  829 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1041 (  _inet		 PGNSP PGUID -1 f b A f t \054 0  869 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 651  (  _cidr		 PGNSP PGUID -1 f b A f t \054 0  650 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1263 (  _cstring	 PGNSP PGUID -1 f b A f t \054 0 2275 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  #define CSTRINGARRAYOID		1263
  
! DATA(insert OID = 1042 ( bpchar		 PGNSP PGUID -1 f b S f t \054 0	0 1014 bpcharin bpcharout bpcharrecv bpcharsend bpchartypmodin bpchartypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("char(length), blank-padded string, fixed storage length");
  #define BPCHAROID		1042
! DATA(insert OID = 1043 ( varchar	 PGNSP PGUID -1 f b S f t \054 0	0 1015 varcharin varcharout varcharrecv varcharsend varchartypmodin varchartypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("varchar(length), non-blank-padded string, variable storage length");
  #define VARCHAROID		1043
  
! DATA(insert OID = 1082 ( date		 PGNSP PGUID	4 t b D f t \054 0	0 1182 date_in date_out date_recv date_send - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("date");
  #define DATEOID			1082
! DATA(insert OID = 1083 ( time		 PGNSP PGUID	8 FLOAT8PASSBYVAL b D f t \054 0	0 1183 time_in time_out time_recv time_send timetypmodin timetypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("time of day");
  #define TIMEOID			1083
  
  /* OIDS 1100 - 1199 */
! DATA(insert OID = 1114 ( timestamp	 PGNSP PGUID	8 FLOAT8PASSBYVAL b D f t \054 0	0 1115 timestamp_in timestamp_out timestamp_recv timestamp_send timestamptypmodin timestamptypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("date and time");
  #define TIMESTAMPOID	1114
! DATA(insert OID = 1115 ( _timestamp  PGNSP PGUID	-1 f b A f t \054 0 1114 0 array_in array_out array_recv array_send timestamptypmodin timestamptypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1182 ( _date		 PGNSP PGUID	-1 f b A f t \054 0 1082 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1183 ( _time		 PGNSP PGUID	-1 f b A f t \054 0 1083 0 array_in array_out array_recv array_send timetypmodin timetypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1184 ( timestamptz PGNSP PGUID	8 FLOAT8PASSBYVAL b D t t \054 0	0 1185 timestamptz_in timestamptz_out timestamptz_recv timestamptz_send timestamptztypmodin timestamptztypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("date and time with time zone");
  #define TIMESTAMPTZOID	1184
! DATA(insert OID = 1185 ( _timestamptz PGNSP PGUID -1 f b A f t \054 0	1184 0 array_in array_out array_recv array_send timestamptztypmodin timestamptztypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1186 ( interval	 PGNSP PGUID 16 f b T t t \054 0	0 1187 interval_in interval_out interval_recv interval_send intervaltypmodin intervaltypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("@ <number> <units>, time interval");
  #define INTERVALOID		1186
! DATA(insert OID = 1187 ( _interval	 PGNSP PGUID	-1 f b A f t \054 0 1186 0 array_in array_out array_recv array_send intervaltypmodin intervaltypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 1200 - 1299 */
! DATA(insert OID = 1231 (  _numeric	 PGNSP PGUID -1 f b A f t \054 0	1700 0 array_in array_out array_recv array_send numerictypmodin numerictypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1266 ( timetz		 PGNSP PGUID 12 f b D f t \054 0	0 1270 timetz_in timetz_out timetz_recv timetz_send timetztypmodin timetztypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("time of day with time zone");
  #define TIMETZOID		1266
! DATA(insert OID = 1270 ( _timetz	 PGNSP PGUID -1 f b A f t \054 0	1266 0 array_in array_out array_recv array_send timetztypmodin timetztypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 1500 - 1599 */
! DATA(insert OID = 1560 ( bit		 PGNSP PGUID -1 f b V f t \054 0	0 1561 bit_in bit_out bit_recv bit_send bittypmodin bittypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("fixed-length bit string");
  #define BITOID	 1560
! DATA(insert OID = 1561 ( _bit		 PGNSP PGUID -1 f b A f t \054 0	1560 0 array_in array_out array_recv array_send bittypmodin bittypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1562 ( varbit		 PGNSP PGUID -1 f b V t t \054 0	0 1563 varbit_in varbit_out varbit_recv varbit_send varbittypmodin varbittypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("variable-length bit string");
  #define VARBITOID	  1562
! DATA(insert OID = 1563 ( _varbit	 PGNSP PGUID -1 f b A f t \054 0	1562 0 array_in array_out array_recv array_send varbittypmodin varbittypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 1600 - 1699 */
  
  /* OIDS 1700 - 1799 */
! DATA(insert OID = 1700 ( numeric	   PGNSP PGUID -1 f b N f t \054 0	0 1231 numeric_in numeric_out numeric_recv numeric_send numerictypmodin numerictypmodout - i m f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("numeric(precision, decimal), arbitrary precision number");
  #define NUMERICOID		1700
  
! DATA(insert OID = 1790 ( refcursor	   PGNSP PGUID -1 f b U f t \054 0	0 2201 textin textout textrecv textsend - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("reference to cursor (portal name)");
  #define REFCURSOROID	1790
  
  /* OIDS 2200 - 2299 */
! DATA(insert OID = 2201 ( _refcursor    PGNSP PGUID -1 f b A f t \054 0 1790 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  
! DATA(insert OID = 2202 ( regprocedure  PGNSP PGUID	4 t b N f t \054 0	 0 2207 regprocedurein regprocedureout regprocedurerecv regproceduresend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered procedure (with args)");
  #define REGPROCEDUREOID 2202
  
! DATA(insert OID = 2203 ( regoper	   PGNSP PGUID	4 t b N f t \054 0	 0 2208 regoperin regoperout regoperrecv regopersend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered operator");
  #define REGOPEROID		2203
  
! DATA(insert OID = 2204 ( regoperator   PGNSP PGUID	4 t b N f t \054 0	 0 2209 regoperatorin regoperatorout regoperatorrecv regoperatorsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered operator (with args)");
  #define REGOPERATOROID	2204
  
! DATA(insert OID = 2205 ( regclass	   PGNSP PGUID	4 t b N f t \054 0	 0 2210 regclassin regclassout regclassrecv regclasssend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered class");
  #define REGCLASSOID		2205
  
! DATA(insert OID = 2206 ( regtype	   PGNSP PGUID	4 t b N f t \054 0	 0 2211 regtypein regtypeout regtyperecv regtypesend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered type");
  #define REGTYPEOID		2206
  
! DATA(insert OID = 2207 ( _regprocedure PGNSP PGUID -1 f b A f t \054 0 2202 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 2208 ( _regoper	   PGNSP PGUID -1 f b A f t \054 0 2203 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 2209 ( _regoperator  PGNSP PGUID -1 f b A f t \054 0 2204 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 2210 ( _regclass	   PGNSP PGUID -1 f b A f t \054 0 2205 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 2211 ( _regtype	   PGNSP PGUID -1 f b A f t \054 0 2206 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  #define REGTYPEARRAYOID 2211
  
  /* uuid */
! DATA(insert OID = 2950 ( uuid			PGNSP PGUID 16 f b U f t \054 0 0 2951 uuid_in uuid_out uuid_recv uuid_send - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("UUID datatype");
! DATA(insert OID = 2951 ( _uuid			PGNSP PGUID -1 f b A f t \054 0 2950 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* text search */
! DATA(insert OID = 3614 ( tsvector		PGNSP PGUID -1 f b U f t \054 0 0 3643 tsvectorin tsvectorout tsvectorrecv tsvectorsend - - ts_typanalyze i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("text representation for text search");
  #define TSVECTOROID		3614
! DATA(insert OID = 3642 ( gtsvector		PGNSP PGUID -1 f b U f t \054 0 0 3644 gtsvectorin gtsvectorout - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("GiST index internal text representation for text search");
  #define GTSVECTOROID	3642
! DATA(insert OID = 3615 ( tsquery		PGNSP PGUID -1 f b U f t \054 0 0 3645 tsqueryin tsqueryout tsqueryrecv tsquerysend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("query representation for text search");
  #define TSQUERYOID		3615
! DATA(insert OID = 3734 ( regconfig		PGNSP PGUID 4 t b N f t \054 0 0 3735 regconfigin regconfigout regconfigrecv regconfigsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered text search configuration");
  #define REGCONFIGOID	3734
! DATA(insert OID = 3769 ( regdictionary	PGNSP PGUID 4 t b N f t \054 0 0 3770 regdictionaryin regdictionaryout regdictionaryrecv regdictionarysend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered text search dictionary");
  #define REGDICTIONARYOID	3769
  
! DATA(insert OID = 3643 ( _tsvector		PGNSP PGUID -1 f b A f t \054 0 3614 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 3644 ( _gtsvector		PGNSP PGUID -1 f b A f t \054 0 3642 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 3645 ( _tsquery		PGNSP PGUID -1 f b A f t \054 0 3615 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 3735 ( _regconfig		PGNSP PGUID -1 f b A f t \054 0 3734 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 3770 ( _regdictionary PGNSP PGUID -1 f b A f t \054 0 3769 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  
! DATA(insert OID = 2970 ( txid_snapshot	PGNSP PGUID -1 f b U f t \054 0 0 2949 txid_snapshot_in txid_snapshot_out txid_snapshot_recv txid_snapshot_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("txid snapshot");
! DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  
  /*
   * pseudo-types
***************
*** 594,624 **** DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 a
   * but there is now support for it in records and arrays.  Perhaps we should
   * just treat it as a regular base type?
   */
! DATA(insert OID = 2249 ( record			PGNSP PGUID -1 f p P f t \054 0 0 2287 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
  #define RECORDOID		2249
! DATA(insert OID = 2287 ( _record		PGNSP PGUID -1 f p P f t \054 0 2249 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
  #define RECORDARRAYOID	2287
! DATA(insert OID = 2275 ( cstring		PGNSP PGUID -2 f p P f t \054 0 0 1263 cstring_in cstring_out cstring_recv cstring_send - - - c p f 0 -1 0 _null_ _null_ ));
  #define CSTRINGOID		2275
! DATA(insert OID = 2276 ( any			PGNSP PGUID  4 t p P f t \054 0 0 0 any_in any_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define ANYOID			2276
! DATA(insert OID = 2277 ( anyarray		PGNSP PGUID -1 f p P f t \054 0 0 0 anyarray_in anyarray_out anyarray_recv anyarray_send - - - d x f 0 -1 0 _null_ _null_ ));
  #define ANYARRAYOID		2277
! DATA(insert OID = 2278 ( void			PGNSP PGUID  4 t p P f t \054 0 0 0 void_in void_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define VOIDOID			2278
! DATA(insert OID = 2279 ( trigger		PGNSP PGUID  4 t p P f t \054 0 0 0 trigger_in trigger_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define TRIGGEROID		2279
! DATA(insert OID = 2280 ( language_handler	PGNSP PGUID  4 t p P f t \054 0 0 0 language_handler_in language_handler_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define LANGUAGE_HANDLEROID		2280
! DATA(insert OID = 2281 ( internal		PGNSP PGUID  SIZEOF_POINTER t p P f t \054 0 0 0 internal_in internal_out - - - - - ALIGNOF_POINTER p f 0 -1 0 _null_ _null_ ));
  #define INTERNALOID		2281
! DATA(insert OID = 2282 ( opaque			PGNSP PGUID  4 t p P f t \054 0 0 0 opaque_in opaque_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define OPAQUEOID		2282
! DATA(insert OID = 2283 ( anyelement		PGNSP PGUID  4 t p P f t \054 0 0 0 anyelement_in anyelement_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define ANYELEMENTOID	2283
! DATA(insert OID = 2776 ( anynonarray	PGNSP PGUID  4 t p P f t \054 0 0 0 anynonarray_in anynonarray_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define ANYNONARRAYOID	2776
! DATA(insert OID = 3500 ( anyenum		PGNSP PGUID  4 t p P f t \054 0 0 0 anyenum_in anyenum_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define ANYENUMOID		3500
  
  
--- 608,638 ----
   * but there is now support for it in records and arrays.  Perhaps we should
   * just treat it as a regular base type?
   */
! DATA(insert OID = 2249 ( record			PGNSP PGUID -1 f p P f t \054 0 0 2287 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  #define RECORDOID		2249
! DATA(insert OID = 2287 ( _record		PGNSP PGUID -1 f p P f t \054 0 2249 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  #define RECORDARRAYOID	2287
! DATA(insert OID = 2275 ( cstring		PGNSP PGUID -2 f p P f t \054 0 0 1263 cstring_in cstring_out cstring_recv cstring_send - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  #define CSTRINGOID		2275
! DATA(insert OID = 2276 ( any			PGNSP PGUID  4 t p P f t \054 0 0 0 any_in any_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define ANYOID			2276
! DATA(insert OID = 2277 ( anyarray		PGNSP PGUID -1 f p P f t \054 0 0 0 anyarray_in anyarray_out anyarray_recv anyarray_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  #define ANYARRAYOID		2277
! DATA(insert OID = 2278 ( void			PGNSP PGUID  4 t p P f t \054 0 0 0 void_in void_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define VOIDOID			2278
! DATA(insert OID = 2279 ( trigger		PGNSP PGUID  4 t p P f t \054 0 0 0 trigger_in trigger_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define TRIGGEROID		2279
! DATA(insert OID = 2280 ( language_handler	PGNSP PGUID  4 t p P f t \054 0 0 0 language_handler_in language_handler_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define LANGUAGE_HANDLEROID		2280
! DATA(insert OID = 2281 ( internal		PGNSP PGUID  SIZEOF_POINTER t p P f t \054 0 0 0 internal_in internal_out - - - - - ALIGNOF_POINTER p f 0 -1 0 -1 f _null_ _null_ ));
  #define INTERNALOID		2281
! DATA(insert OID = 2282 ( opaque			PGNSP PGUID  4 t p P f t \054 0 0 0 opaque_in opaque_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define OPAQUEOID		2282
! DATA(insert OID = 2283 ( anyelement		PGNSP PGUID  4 t p P f t \054 0 0 0 anyelement_in anyelement_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define ANYELEMENTOID	2283
! DATA(insert OID = 2776 ( anynonarray	PGNSP PGUID  4 t p P f t \054 0 0 0 anynonarray_in anynonarray_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define ANYNONARRAYOID	2776
! DATA(insert OID = 3500 ( anyenum		PGNSP PGUID  4 t p P f t \054 0 0 0 anyenum_in anyenum_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define ANYENUMOID		3500
  
  
*** a/src/include/catalog/pg_type_fn.h
--- b/src/include/catalog/pg_type_fn.h
***************
*** 50,56 **** extern Oid TypeCreate(Oid newTypeOid,
  		   char storage,
  		   int32 typeMod,
  		   int32 typNDims,
! 		   bool typeNotNull);
  
  extern void GenerateTypeDependencies(Oid typeNamespace,
  						 Oid typeObjectId,
--- 50,58 ----
  		   char storage,
  		   int32 typeMod,
  		   int32 typNDims,
!     	   bool typeNotNull,
! 		   int32 typeNLabels,
! 		   bool typeSorted);
  
  extern void GenerateTypeDependencies(Oid typeNamespace,
  						 Oid typeObjectId,
*** a/src/include/commands/typecmds.h
--- b/src/include/commands/typecmds.h
***************
*** 24,29 **** extern void RemoveTypes(DropStmt *drop);
--- 24,30 ----
  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);
  
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
***************
*** 346,351 **** typedef enum NodeTag
--- 346,352 ----
  	T_AlterUserMappingStmt,
  	T_DropUserMappingStmt,
  	T_AlterTableSpaceOptionsStmt,
+ 	T_AlterEnumStmt,
  
  	/*
  	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 2171,2176 **** typedef struct CreateEnumStmt
--- 2171,2190 ----
  
  
  /* ----------------------
+  *		Alter Type Statement, enum types
+  * ----------------------
+  */
+ typedef struct AlterEnumStmt
+ {
+ 	NodeTag		type;
+ 	List	   *typeName;		/* qualified name (list of Value strings) */
+ 	char	   *newVal;			/* new enum value */
+ 	char	   *newValNeighbour;/* neighbouring enum value */
+ 	bool	    newValIsAfter;	/* new enum value is after neighbour? */
+ } AlterEnumStmt;
+ 
+ 
+ /* ----------------------
   *		Create View Statement
   * ----------------------
   */
*** a/src/test/regress/expected/enum.out
--- b/src/test/regress/expected/enum.out
***************
*** 25,30 **** ERROR:  invalid input value for enum rainbow: "mauve"
--- 25,108 ----
  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 typnlabels, typsorted
+ FROM pg_type
+ WHERE oid = 'planets'::regtype;
+  typnlabels | typsorted 
+ ------------+-----------
+           4 | t
+ (1 row)
+ 
+ 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 typnlabels, typsorted
+ FROM pg_type
+ WHERE oid = 'planets'::regtype;
+  typnlabels | typsorted 
+ ------------+-----------
+           8 | f
+ (1 row)
+ 
+ SELECT enumlabel, enumsortorder 
+ FROM pg_enum 
+ WHERE enumtypid = 'planets'::regtype
+ ORDER by 2;
+  enumlabel | enumsortorder 
+ -----------+---------------
+  mercury   |             1
+  venus     |             2
+  earth     |             3
+  mars      |             4
+  jupiter   |             5
+  saturn    |             6
+  uranus    |             7
+  neptune   |             8
+ (8 rows)
+ 
+ select 'mars'::planets > 'mercury' as using_sortorder;
+  using_sortorder 
+ -----------------
+  t
+ (1 row)
+ 
+ -- 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 label.
+ DROP TYPE planets;
+ --
  -- Basic table creation, row selection
  --
  CREATE TABLE enumtest (col rainbow);
***************
*** 403,409 **** 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 
! -----------+-----------
  (0 rows)
  
--- 481,487 ----
  
  SELECT * FROM pg_enum WHERE NOT EXISTS
    (SELECT 1 FROM pg_type WHERE pg_type.oid = enumtypid);
!  enumtypid | enumlabel | enumsortorder 
! -----------+-----------+---------------
  (0 rows)
  
*** a/src/test/regress/sql/enum.sql
--- b/src/test/regress/sql/enum.sql
***************
*** 16,21 **** SELECT 'red'::rainbow;
--- 16,69 ----
  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 typnlabels, typsorted
+ FROM pg_type
+ WHERE oid = 'planets'::regtype;
+ 
+ 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 typnlabels, typsorted
+ FROM pg_type
+ WHERE oid = 'planets'::regtype;
+ 
+ SELECT enumlabel, enumsortorder 
+ FROM pg_enum 
+ WHERE enumtypid = 'planets'::regtype
+ ORDER by 2;
+ 
+ select 'mars'::planets > 'mercury' as using_sortorder;
+ 
+ -- errors for adding labels 
+ ALTER TYPE planets ADD 
+ 	  'plutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutopluto';
+ 
+ ALTER TYPE planets ADD 'pluto' AFTER 'zeus';
+ 
+ DROP TYPE planets;
+ --
  -- Basic table creation, row selection
  --
  CREATE TABLE enumtest (col rainbow);
#31Andrew Dunstan
andrew@dunslane.net
In reply to: Andrew Dunstan (#30)
1 attachment(s)
Re: WIP: extensible enums

Attached is a a slightly updated version of this with the bitrot fixed.

cheers

andrew

Attachments:

venum3.patchtext/x-patch; name=venum3.patchDownload
*** a/contrib/pg_upgrade/function.c
--- b/contrib/pg_upgrade/function.c
***************
*** 61,85 **** install_support_functions(migratorContext *ctx)
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 				"		binary_upgrade.set_next_heap_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_toast_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_index_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			 "		binary_upgrade.add_pg_enum_label(OID, OID, NAME) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
--- 61,85 ----
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 					 "		binary_upgrade.set_next_pg_enum_oid(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 				"		binary_upgrade.set_next_heap_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_toast_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_index_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
*** a/contrib/pg_upgrade_support/pg_upgrade_support.c
--- b/contrib/pg_upgrade_support/pg_upgrade_support.c
***************
*** 30,35 **** PG_MODULE_MAGIC;
--- 30,36 ----
  extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
  extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_array_oid;
  extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_toast_oid;
+ extern PGDLLIMPORT Oid binary_upgrade_next_pg_enum_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;
***************
*** 37,54 **** extern PGDLLIMPORT Oid binary_upgrade_next_index_relfilenode;
  Datum		set_next_pg_type_oid(PG_FUNCTION_ARGS);
  Datum		set_next_pg_type_array_oid(PG_FUNCTION_ARGS);
  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);
  
  PG_FUNCTION_INFO_V1(set_next_pg_type_oid);
  PG_FUNCTION_INFO_V1(set_next_pg_type_array_oid);
  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);
  
  Datum
  set_next_pg_type_oid(PG_FUNCTION_ARGS)
--- 38,55 ----
  Datum		set_next_pg_type_oid(PG_FUNCTION_ARGS);
  Datum		set_next_pg_type_array_oid(PG_FUNCTION_ARGS);
  Datum		set_next_pg_type_toast_oid(PG_FUNCTION_ARGS);
+ Datum       set_next_pg_enum_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);
  
  PG_FUNCTION_INFO_V1(set_next_pg_type_oid);
  PG_FUNCTION_INFO_V1(set_next_pg_type_array_oid);
  PG_FUNCTION_INFO_V1(set_next_pg_type_toast_oid);
+ PG_FUNCTION_INFO_V1(set_next_pg_enum_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);
  
  Datum
  set_next_pg_type_oid(PG_FUNCTION_ARGS)
***************
*** 81,86 **** set_next_pg_type_toast_oid(PG_FUNCTION_ARGS)
--- 82,97 ----
  }
  
  Datum
+ set_next_pg_enum_oid(PG_FUNCTION_ARGS)
+ {
+ 	Oid			enumoid = PG_GETARG_OID(0);
+ 
+ 	binary_upgrade_next_pg_enum_oid = enumoid;
+ 
+ 	PG_RETURN_VOID();
+ }
+ 
+ Datum
  set_next_heap_relfilenode(PG_FUNCTION_ARGS)
  {
  	Oid			relfilenode = PG_GETARG_OID(0);
***************
*** 110,124 **** set_next_index_relfilenode(PG_FUNCTION_ARGS)
  	PG_RETURN_VOID();
  }
  
- Datum
- add_pg_enum_label(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);
- 
- 	PG_RETURN_VOID();
- }
--- 121,123 ----
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 855,861 **** AddNewRelationType(const char *typeName,
  				   'x',			/* fully TOASTable */
  				   -1,			/* typmod */
  				   0,			/* array dimensions for typBaseType */
! 				   false);		/* Type NOT NULL */
  }
  
  /* --------------------------------
--- 855,863 ----
  				   'x',			/* fully TOASTable */
  				   -1,			/* typmod */
  				   0,			/* array dimensions for typBaseType */
! 				   false,		/* Type NOT NULL */
! 				   -1,          /* no enum labels */
! 				   false);      /* type is not an enum, so not sorted */
  }
  
  /* --------------------------------
***************
*** 1108,1114 **** heap_create_with_catalog(const char *relname,
  				   'x',			/* fully TOASTable */
  				   -1,			/* typmod */
  				   0,			/* array dimensions for typBaseType */
! 				   false);		/* Type NOT NULL */
  
  		pfree(relarrayname);
  	}
--- 1110,1118 ----
  				   'x',			/* fully TOASTable */
  				   -1,			/* typmod */
  				   0,			/* array dimensions for typBaseType */
! 				   false,		/* Type NOT NULL */
! 				   -1,          /* no enum labels */
! 				   false);      /* type is not an enum, so not sorted */
  
  		pfree(relarrayname);
  	}
*** a/src/backend/catalog/pg_enum.c
--- b/src/backend/catalog/pg_enum.c
***************
*** 18,29 ****
--- 18,219 ----
  #include "catalog/catalog.h"
  #include "catalog/indexing.h"
  #include "catalog/pg_enum.h"
+ #include "catalog/pg_type.h"
+ #include "utils/lsyscache.h"
+ #include "utils/syscache.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
  #include "utils/rel.h"
  #include "utils/tqual.h"
  
  static int	oid_cmp(const void *p1, const void *p2);
+ static int	sort_order_cmp(const void *p1, const void *p2);
+ 
+ Oid      binary_upgrade_next_pg_enum_oid = InvalidOid;
+ 
+ /*
+  * 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.
+  *
+  * Returns true iff the labels are sorted by oid after the addition.
+  */
+ 
+ bool
+ AddEnumLabel(Oid enumTypeOid, 
+ 			 char *newVal, 
+ 			 char *neighbour, 
+ 			 bool newValIsAfter,
+ 			 int  nelems,
+ 			 bool elems_are_sorted)
+ {
+ 	Oid        newOid;
+ 	Relation   pg_enum;
+ 	TupleDesc	tupDesc;
+ 	Datum		values[Natts_pg_enum];
+ 	bool		nulls[Natts_pg_enum];
+ 	NameData	enumlabel;
+ 	HeapTuple   enum_tup;
+ 	bool        result = elems_are_sorted;
+ 	int         newelemorder;
+ 
+ 	/* 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)));
+ 
+ 	/* get a new OID for the label */
+ 	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
+ 
+ 	if (OidIsValid(binary_upgrade_next_pg_enum_oid))
+ 	{
+ 		if (neighbour != NULL)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("BEFORE/AFTER incompatible with binary upgrade.")));
+ 
+ 		newOid = binary_upgrade_next_pg_enum_oid;
+ 		binary_upgrade_next_pg_enum_oid = InvalidOid;
+ 	}
+ 	else
+ 	{
+ 		/* non upgrade case */
+ 		newOid = GetNewOid(pg_enum);
+ 	}
+ 
+ 	if (neighbour == NULL)
+ 	{
+ 		/* 
+ 		 * Put the new label at the end of the list.
+ 		 * No change to existing tuples is required.
+ 		 */
+ 		newelemorder = nelems + 1;
+ 		/* are the elements still sorted? */
+ 		if (elems_are_sorted)
+ 		{	
+ 			CatCList   *list;
+ 			int			i;
+ 			bool        still_sorted = true;
+ 
+ 			list = SearchSysCacheList1(ENUMTYPOIDNAME, 
+ 									   ObjectIdGetDatum(enumTypeOid));
+ 			for (i = 0; i < nelems; i++)
+ 			{
+ 				HeapTuple tup = &(list->members[i]->tuple);
+ 				Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
+ 
+ 				if (en->enumsortorder == nelems)
+ 				{
+ 					if (HeapTupleGetOid(tup) > newOid)
+ 						still_sorted = false;
+ 					break;
+ 				}
+ 			}
+ 			ReleaseCatCacheList(list);
+ 			if (! still_sorted)
+ 				result = false;
+ 		}
+ 	}
+ 	else 
+ 	{
+ 		/* BEFORE or AFTER specified */
+ 		CatCList   *list;
+ 		int			i;
+ 		HeapTuple    *existing;
+ 		HeapTuple     nbr = NULL;
+ 		Form_pg_enum nbr_en;
+ 
+ 		/* get a list of the existing elements and sort them by enumsortorder */
+ 		list = SearchSysCacheList1(ENUMTYPOIDNAME, 
+ 								   ObjectIdGetDatum(enumTypeOid));
+ 		existing = palloc(nelems * sizeof(HeapTuple));
+ 
+ 		for (i = 0; i < nelems; i++)
+ 			existing[i] = &(list->members[i]->tuple);
+ 
+ 		qsort(existing, nelems, sizeof(HeapTuple), sort_order_cmp);
+ 		
+ 		/* locate the neighbour element */
+ 		for (i = 0; i < nelems; i++)
+ 		{
+ 			Form_pg_enum exists_en;
+ 			exists_en = (Form_pg_enum) GETSTRUCT(existing[i]);
+ 			if (strcmp(NameStr(exists_en->enumlabel),neighbour) == 0)
+ 				nbr = existing[i];
+ 
+ 		}
+ 
+ 		if (nbr == NULL)
+ 		{
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("\"%s\" is not an existing label.", neighbour)));
+ 		}
+ 
+ 		nbr_en = (Form_pg_enum) GETSTRUCT(nbr);
+ 
+ 		/* 
+ 		 * If BEFORE was specified, the new label goes in the neighbour's
+ 		 * position. Otherwise, it goes in the position after that.
+ 		 */
+ 		newelemorder = nbr_en->enumsortorder;
+ 		if (newValIsAfter)
+ 			newelemorder++;
+ 
+ 		/* 
+ 		 * Add 1 to the sortorder of all the labels after where the
+ 		 * new label goes. Do it from the end back so we don't get
+ 		 * uniqueness violations.
+ 		 */
+ 		for (i = nelems - 1; i>= 0; i--)
+ 		{
+ 			HeapTuple newtup;
+ 			Form_pg_enum exst_en = (Form_pg_enum) GETSTRUCT(existing[i]);
+ 			if (exst_en->enumsortorder < newelemorder)
+ 				break;
+ 			
+ 			newtup = heap_copytuple(existing[i]);
+ 			exst_en = (Form_pg_enum) GETSTRUCT(newtup);
+ 			exst_en->enumsortorder ++;
+ 
+ 			simple_heap_update(pg_enum, &newtup->t_self, newtup);
+ 
+ 			CatalogUpdateIndexes(pg_enum, newtup);
+ 
+ 		}
+ 
+ 		ReleaseCatCacheList(list);
+ 
+ 		/* are the labels sorted by OID? */
+ 		if (result && newelemorder > 1)
+ 			result = newOid > HeapTupleGetOid(existing[newelemorder-2]);
+ 		if (result && newelemorder < nelems + 1)
+ 			result = newOid < HeapTupleGetOid(existing[newelemorder-1]);
+  
+ 	}
+ 
+ 	/* set up the new entry */
+ 	tupDesc = pg_enum->rd_att;
+ 	memset(nulls, false, sizeof(nulls));
+ 	values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
+ 	namestrcpy(&enumlabel, newVal);
+ 	values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
+ 	values[Anum_pg_enum_enumsortorder -1] =	Int32GetDatum(newelemorder);
+ 	enum_tup = heap_form_tuple(tupDesc, 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);
+ 
+ 	return result;
+ 
+ }
  
  
  /*
***************
*** 33,40 **** 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)
  {
  	Relation	pg_enum;
  	TupleDesc	tupDesc;
--- 223,229 ----
   * vals is a list of Value strings.
   */
  void
! EnumValuesCreate(Oid enumTypeOid, List *vals)
  {
  	Relation	pg_enum;
  	TupleDesc	tupDesc;
***************
*** 50,58 **** EnumValuesCreate(Oid enumTypeOid, List *vals,
  	num_elems = list_length(vals);
  
  	/*
! 	 * XXX 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?
  	 */
  
  	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
--- 239,247 ----
  	num_elems = list_length(vals);
  
  	/*
! 	 * 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.
! 	 * It is probably not worth trying harder.
  	 */
  
  	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
***************
*** 62,96 **** EnumValuesCreate(Oid enumTypeOid, List *vals,
  	 * Allocate oids
  	 */
  	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
  	{
  		/*
! 		 * 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.
  		 */
! 		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);
  	}
  
  	/* and make the entries */
  	memset(nulls, false, sizeof(nulls));
--- 251,274 ----
  	 * Allocate oids
  	 */
  	oids = (Oid *) palloc(num_elems * sizeof(Oid));
! 
! 	/*
! 	 * 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.
! 	 */
! 	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);
  
  	/* and make the entries */
  	memset(nulls, false, sizeof(nulls));
***************
*** 114,119 **** EnumValuesCreate(Oid enumTypeOid, List *vals,
--- 292,298 ----
  		values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
  		namestrcpy(&enumlabel, lab);
  		values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
+ 		values[Anum_pg_enum_enumsortorder -1] = Int32GetDatum(elemno+1);
  
  		tup = heap_form_tuple(tupDesc, values, nulls);
  		HeapTupleSetOid(tup, oids[elemno]);
***************
*** 164,170 **** EnumValuesDelete(Oid enumTypeOid)
  }
  
  
! /* qsort comparison function */
  static int
  oid_cmp(const void *p1, const void *p2)
  {
--- 343,349 ----
  }
  
  
! /* qsort comparison for oids */
  static int
  oid_cmp(const void *p1, const void *p2)
  {
***************
*** 177,179 **** oid_cmp(const void *p1, const void *p2)
--- 356,371 ----
  		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);
+ 
+ 	return en1->enumsortorder - en2->enumsortorder;
+ }
+ 
*** a/src/backend/catalog/pg_type.c
--- b/src/backend/catalog/pg_type.c
***************
*** 112,117 **** TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId)
--- 112,119 ----
  	values[i++] = ObjectIdGetDatum(InvalidOid); /* typbasetype */
  	values[i++] = Int32GetDatum(-1);	/* typtypmod */
  	values[i++] = Int32GetDatum(0);		/* typndims */
+ 	values[i++] = Int32GetDatum(-1);	/* typnlabels */
+ 	values[i++] = BoolGetDatum(false);	/* typsorted */
  	nulls[i++] = true;			/* typdefaultbin */
  	nulls[i++] = true;			/* typdefault */
  
***************
*** 204,210 **** TypeCreate(Oid newTypeOid,
  		   char storage,
  		   int32 typeMod,
  		   int32 typNDims,		/* Array dimensions for baseType */
! 		   bool typeNotNull)
  {
  	Relation	pg_type_desc;
  	Oid			typeObjectId;
--- 206,214 ----
  		   char storage,
  		   int32 typeMod,
  		   int32 typNDims,		/* Array dimensions for baseType */
! 		   bool typeNotNull,
! 		   int32 typeNLabels,
! 		   bool typeSorted)
  {
  	Relation	pg_type_desc;
  	Oid			typeObjectId;
***************
*** 342,347 **** TypeCreate(Oid newTypeOid,
--- 346,353 ----
  	values[i++] = ObjectIdGetDatum(baseType);	/* typbasetype */
  	values[i++] = Int32GetDatum(typeMod);		/* typtypmod */
  	values[i++] = Int32GetDatum(typNDims);		/* typndims */
+ 	values[i++] = Int32GetDatum(typeNLabels);	/* typnlabels */
+ 	values[i++] = BoolGetDatum(typeSorted);	    /* typsorted */
  
  	/*
  	 * initialize the default binary value for this type.  Check for nulls of
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
***************
*** 85,96 **** 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 char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
  					Oid baseTypeOid,
  					int typMod, Constraint *constr,
  					char *domainName);
- 
- 
  /*
   * DefineType
   *		Registers a new base type.
--- 85,95 ----
  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 checkEnumOwner(HeapTuple tup, TypeName *typename);
  static char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
  					Oid baseTypeOid,
  					int typMod, Constraint *constr,
  					char *domainName);
  /*
   * DefineType
   *		Registers a new base type.
***************
*** 562,569 **** DefineType(List *names, List *parameters)
  				   storage,		/* TOAST strategy */
  				   -1,			/* typMod (Domains only) */
  				   0,			/* Array Dimensions of typbasetype */
! 				   false);		/* Type NOT NULL */
! 
  	/*
  	 * Create the array type that goes with it.
  	 */
--- 561,569 ----
  				   storage,		/* TOAST strategy */
  				   -1,			/* typMod (Domains only) */
  				   0,			/* Array Dimensions of typbasetype */
! 				   false,		/* Type NOT NULL */
! 				   -1,          /* no enum labels */
! 				   false);        /* type is not an enum, so not sorted */
  	/*
  	 * Create the array type that goes with it.
  	 */
***************
*** 601,607 **** DefineType(List *names, List *parameters)
  			   'x',				/* ARRAY is always toastable */
  			   -1,				/* typMod (Domains only) */
  			   0,				/* Array dimensions of typbasetype */
! 			   false);			/* Type NOT NULL */
  
  	pfree(array_type);
  }
--- 601,609 ----
  			   'x',				/* ARRAY is always toastable */
  			   -1,				/* typMod (Domains only) */
  			   0,				/* Array dimensions of typbasetype */
! 			   false,			/* Type NOT NULL */
! 			   -1,              /* no enum labels */
! 			   false);          /* type is not an enum, so not sorted */
  
  	pfree(array_type);
  }
***************
*** 1044,1050 **** DefineDomain(CreateDomainStmt *stmt)
  				   storage,		/* TOAST strategy */
  				   basetypeMod, /* typeMod value */
  				   typNDims,	/* Array dimensions for base type */
! 				   typNotNull); /* Type NOT NULL */
  
  	/*
  	 * Process constraints which refer to the domain ID returned by TypeCreate
--- 1046,1054 ----
  				   storage,		/* TOAST strategy */
  				   basetypeMod, /* typeMod value */
  				   typNDims,	/* Array dimensions for base type */
! 				   typNotNull,  /* Type NOT NULL */
! 				   -1,          /* no enum labels */
! 				   false);      /* type is not an enum, so not sorted */
  
  	/*
  	 * Process constraints which refer to the domain ID returned by TypeCreate
***************
*** 1094,1099 **** DefineEnum(CreateEnumStmt *stmt)
--- 1098,1106 ----
  	AclResult	aclresult;
  	Oid			old_type_oid;
  	Oid			enumArrayOid;
+ 	int         num_labels;
+ 
+ 	num_labels = list_length(stmt->vals);
  
  	/* Convert list of names to a name and namespace */
  	enumNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
***************
*** 1153,1162 **** DefineEnum(CreateEnumStmt *stmt)
  				   'p',			/* TOAST strategy always plain */
  				   -1,			/* typMod (Domains only) */
  				   0,			/* Array dimensions of typbasetype */
! 				   false);		/* Type NOT NULL */
  
  	/* Enter the enum's values into pg_enum */
! 	EnumValuesCreate(enumTypeOid, stmt->vals, InvalidOid);
  
  	/*
  	 * Create the array type that goes with it.
--- 1160,1171 ----
  				   'p',			/* TOAST strategy always plain */
  				   -1,			/* typMod (Domains only) */
  				   0,			/* Array dimensions of typbasetype */
! 				   false,		/* Type NOT NULL */
! 				   num_labels,  /* count enum labels */
! 				   true);       /* enums always start sorted */
  
  	/* Enter the enum's values into pg_enum */
! 	EnumValuesCreate(enumTypeOid, stmt->vals);
  
  	/*
  	 * Create the array type that goes with it.
***************
*** 1192,1202 **** DefineEnum(CreateEnumStmt *stmt)
  			   'x',				/* ARRAY is always toastable */
  			   -1,				/* typMod (Domains only) */
  			   0,				/* Array dimensions of typbasetype */
! 			   false);			/* Type NOT NULL */
  
  	pfree(enumArrayName);
  }
  
  
  /*
   * Find suitable I/O functions for a type.
--- 1201,1288 ----
  			   'x',				/* ARRAY is always toastable */
  			   -1,				/* typMod (Domains only) */
  			   0,				/* Array dimensions of typbasetype */
! 			   false,			/* Type NOT NULL */
! 			   -1,              /* no enum labels */
! 			   false);          /* type is not an enum, so not sorted */
  
  	pfree(enumArrayName);
  }
  
+ /*
+  * AlterEnum
+  *		Registers a new label for an existing enum.
+  */
+ void
+ AlterEnum (AlterEnumStmt *stmt)
+ {
+ 	Oid			enum_type_oid;
+ 	TypeName  *typename;
+ 	bool       sorted;
+ 	HeapTuple tup, newtup;
+ 	Relation  rel;
+ 	Form_pg_type typTup;
+ 
+ 	/* Make a TypeName so we can use standard type lookup machinery */
+ 	typename = makeTypeNameFromNameList(stmt->typeName);
+ 	enum_type_oid = typenameTypeId(NULL, typename, NULL);
+ 
+ 	/* Look up the row in the type table */
+ 	rel = heap_open(TypeRelationId, RowExclusiveLock);
+ 
+ 	tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(enum_type_oid));
+ 	if (!HeapTupleIsValid(tup))
+ 		elog(ERROR, "cache lookup failed for type %u", enum_type_oid);
+ 
+ 	/* Copy the syscache entry so we can scribble on it below */
+ 	newtup = heap_copytuple(tup);
+ 	ReleaseSysCache(tup);
+ 	tup = newtup;
+ 	typTup = (Form_pg_type) GETSTRUCT(tup);
+ 
+ 	/* Check it's an enum and check user has permission to ALTER the enum */
+ 	checkEnumOwner(tup, typename);
+ 
+ 	/* Add the new label */
+ 	sorted = AddEnumLabel (enum_type_oid, stmt->newVal, 
+ 						   stmt->newValNeighbour, stmt->newValIsAfter,
+ 						   typTup->typnlabels, typTup->typsorted);
+ 
+ 	/* Update the row in pg_type */
+ 	typTup->typnlabels += 1;
+ 	typTup->typsorted = sorted;
+ 
+ 	simple_heap_update(rel, &tup->t_self, tup);
+ 
+ 	CatalogUpdateIndexes(rel, tup);
+ 
+ 	/* Clean up */
+ 	heap_close(rel, RowExclusiveLock);
+ }
+ 
+ 
+ /*
+  * 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, TypeName *typename)
+ {
+ 	Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup);
+ 
+ 	/* Check that this is actually a domain */
+ 	if (typTup->typtype != TYPTYPE_ENUM)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 				 errmsg("\"%s\" is not an enum",
+ 						TypeNameToString(typename))));
+ 
+ 	/* 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.
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2873,2878 **** _copyCreateEnumStmt(CreateEnumStmt *from)
--- 2873,2891 ----
  	return newnode;
  }
  
+ static AlterEnumStmt *
+ _copyAlterEnumStmt(AlterEnumStmt *from)
+ {
+ 	AlterEnumStmt *newnode = makeNode(AlterEnumStmt);
+ 
+ 	COPY_NODE_FIELD(typeName);
+ 	COPY_STRING_FIELD(newVal);
+ 	COPY_STRING_FIELD(newValNeighbour);
+ 	COPY_SCALAR_FIELD(newValIsAfter);
+ 
+ 	return newnode;
+ }
+ 
  static ViewStmt *
  _copyViewStmt(ViewStmt *from)
  {
***************
*** 4033,4038 **** copyObject(void *from)
--- 4046,4054 ----
  		case T_CreateEnumStmt:
  			retval = _copyCreateEnumStmt(from);
  			break;
+ 		case T_AlterEnumStmt:
+ 			retval = _copyAlterEnumStmt(from);
+ 			break;
  		case T_ViewStmt:
  			retval = _copyViewStmt(from);
  			break;
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 1390,1395 **** _equalCreateEnumStmt(CreateEnumStmt *a, CreateEnumStmt *b)
--- 1390,1406 ----
  }
  
  static bool
+ _equalAlterEnumStmt(AlterEnumStmt *a, AlterEnumStmt *b)
+ {
+ 	COMPARE_NODE_FIELD(typeName);
+ 	COMPARE_STRING_FIELD(newVal);
+ 	COMPARE_STRING_FIELD(newValNeighbour);
+ 	COMPARE_SCALAR_FIELD(newValIsAfter);
+ 
+ 	return true;
+ }
+ 
+ static bool
  _equalViewStmt(ViewStmt *a, ViewStmt *b)
  {
  	COMPARE_NODE_FIELD(view);
***************
*** 2697,2702 **** equal(void *a, void *b)
--- 2708,2716 ----
  		case T_CreateEnumStmt:
  			retval = _equalCreateEnumStmt(a, b);
  			break;
+ 		case T_AlterEnumStmt:
+ 			retval = _equalAlterEnumStmt(a, b);
+ 			break;
  		case T_ViewStmt:
  			retval = _equalViewStmt(a, b);
  			break;
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 182,189 **** static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
  }
  
  %type <node>	stmt schema_stmt
! 		AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterFdwStmt
! 		AlterForeignServerStmt AlterGroupStmt
  		AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
  		AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
  		AlterRoleStmt AlterRoleSetStmt
--- 182,189 ----
  }
  
  %type <node>	stmt schema_stmt
!         AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
!         AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
  		AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
  		AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
  		AlterRoleStmt AlterRoleSetStmt
***************
*** 652,657 **** stmt :
--- 652,658 ----
  			| AlterDatabaseSetStmt
  			| AlterDefaultPrivilegesStmt
  			| AlterDomainStmt
+ 			| AlterEnumStmt
  			| AlterFdwStmt
  			| AlterForeignServerStmt
  			| AlterFunctionStmt
***************
*** 3861,3866 **** enum_val_list:	Sconst
--- 3862,3907 ----
  				{ $$ = 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->newValNeighbour = 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->newValNeighbour = $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->newValNeighbour = $7;
+ 				 n->newValIsAfter = true;
+ 
+ 				 $$ = (Node *) n;
+ 			 }
+ 		 ;
+ 
+ 
  
  /*****************************************************************************
   *
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 190,195 **** check_xact_readonly(Node *parsetree)
--- 190,196 ----
  		case T_CreateTrigStmt:
  		case T_CompositeTypeStmt:
  		case T_CreateEnumStmt:
+ 		case T_AlterEnumStmt:
  		case T_ViewStmt:
  		case T_DropCastStmt:
  		case T_DropStmt:
***************
*** 860,865 **** standard_ProcessUtility(Node *parsetree,
--- 861,870 ----
  			DefineEnum((CreateEnumStmt *) parsetree);
  			break;
  
+ 		case T_AlterEnumStmt:	/* ALTER TYPE (enum) */
+ 			AlterEnum((AlterEnumStmt *) parsetree);
+ 			break;
+ 
  		case T_ViewStmt:		/* CREATE VIEW */
  			DefineView((ViewStmt *) parsetree, queryString);
  			break;
***************
*** 1868,1873 **** CreateCommandTag(Node *parsetree)
--- 1873,1882 ----
  			tag = "CREATE TYPE";
  			break;
  
+ 		case T_AlterEnumStmt:
+ 			tag = "ALTER TYPE";
+ 			break;
+ 
  		case T_ViewStmt:
  			tag = "CREATE VIEW";
  			break;
***************
*** 2410,2415 **** GetCommandLogLevel(Node *parsetree)
--- 2419,2428 ----
  			lev = LOGSTMT_DDL;
  			break;
  
+ 		case T_AlterEnumStmt:
+ 			lev = LOGSTMT_DDL;
+ 			break;
+ 
  		case T_ViewStmt:
  			lev = LOGSTMT_DDL;
  			break;
*** a/src/backend/utils/adt/enum.c
--- b/src/backend/utils/adt/enum.c
***************
*** 14,19 ****
--- 14,20 ----
  #include "postgres.h"
  
  #include "catalog/pg_enum.h"
+ #include "catalog/pg_type.h"
  #include "fmgr.h"
  #include "utils/array.h"
  #include "utils/builtins.h"
***************
*** 22,30 ****
  #include "libpq/pqformat.h"
  #include "miscadmin.h"
  
  
  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 */
--- 23,48 ----
  #include "libpq/pqformat.h"
  #include "miscadmin.h"
  
+ typedef struct 
+ {
+ 	Oid      enum_oid;
+ 	int32   sort_order;
+ } enum_sort;
+ 
+ typedef struct 
+ {
+ 	Oid       enumtypoid;
+ 	bool      oids_are_sorted;
+ 	int       sort_list_length;
+ 	int       label_count;
+ 	enum_sort sort_order_list[1];
+ } enum_sort_cache;
+ 	
+ 
  
  static ArrayType *enum_range_internal(Oid enumtypoid, Oid lower, Oid upper);
! static int	enum_sort_cmp(const void *left, const void *right);
! static int	enum_oid_cmp(const void *left, const void *right);
  
  
  /* Basic I/O support */
***************
*** 155,167 **** enum_send(PG_FUNCTION_ARGS)
  
  /* Comparison functions and related */
  
  Datum
  enum_lt(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a < b);
  }
  
  Datum
--- 173,314 ----
  
  /* Comparison functions and related */
  
+ static inline int 
+ enum_ccmp(Oid arg1, Oid arg2, FunctionCallInfo fcinfo)
+ {
+ 
+ 	enum_sort_cache * mycache;
+ 	enum_sort *es1, *es2, srch;
+ 	int sort1, sort2;
+ 	bool added = false;
+ 	HeapTuple	enum_tup, type_tup;
+ 	Form_pg_enum en;
+ 	Oid typeoid;
+ 	Form_pg_type typ;
+ 
+ 	if (arg1 == arg2)
+ 		return 0;
+ 
+ 	mycache = (enum_sort_cache *) fcinfo->flinfo->fn_extra;
+ 	if (mycache == NULL )
+ 	{
+ 		enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg1));
+ 		en = (Form_pg_enum) GETSTRUCT(enum_tup);
+ 		typeoid = en->enumtypid;
+ 		type_tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typeoid));
+ 		typ = (Form_pg_type)  GETSTRUCT(type_tup);
+ 		if (typ->typtype != 'e')
+ 			elog(ERROR,"wrong type for oid %u",typeoid);
+         fcinfo->flinfo->fn_extra = 
+ 		  MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ 							 sizeof(enum_sort_cache) + 
+ 							 (typ->typnlabels * sizeof(enum_sort)));
+         mycache = (enum_sort_cache *) fcinfo->flinfo->fn_extra;
+ 		mycache->enumtypoid = typeoid;
+ 		mycache->sort_list_length = 1;
+ 		mycache->label_count = typ->typnlabels;
+ 		mycache->oids_are_sorted = typ->typsorted;
+ 		mycache->sort_order_list[0].enum_oid = arg1;
+ 		mycache->sort_order_list[0].sort_order = en->enumsortorder;
+ 		ReleaseSysCache(type_tup);
+ 		ReleaseSysCache(enum_tup);
+ 	}
+ 
+ 	if (mycache->oids_are_sorted)
+ 		return arg1 - arg2;
+ 
+ 	srch.enum_oid = arg1;
+ 	es1 = bsearch(&srch,
+ 				  mycache->sort_order_list,
+ 				  mycache->sort_list_length,
+ 				  sizeof(enum_sort),
+ 				  enum_oid_cmp);
+ 	srch.enum_oid = arg2;
+ 	es2 = bsearch(&srch,
+ 				  mycache->sort_order_list,
+ 				  mycache->sort_list_length,
+ 				  sizeof(enum_sort),
+ 				  enum_oid_cmp);
+ 
+ 	if (es1 == NULL)
+ 	{
+ 		
+ 		enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg1));
+ 		en = (Form_pg_enum) GETSTRUCT(enum_tup);
+ 		mycache->sort_order_list[mycache->sort_list_length].enum_oid = arg1;
+ 		sort1 = en->enumsortorder;
+ 		mycache->sort_order_list[mycache->sort_list_length].sort_order = 
+ 			sort1; 
+ 		ReleaseSysCache(enum_tup);
+ 		mycache->sort_list_length ++;
+ 		added = true;
+ 	}
+ 	else
+ 	{
+ 		/* already in cache */
+ 		sort1 = es1->sort_order;
+ 	}
+ 
+ 	if (es2 == NULL)
+ 	{
+ 		
+ 		enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg2));
+ 		en = (Form_pg_enum) GETSTRUCT(enum_tup);
+ 		sort2 = en->enumsortorder;
+ 		mycache->sort_order_list[mycache->sort_list_length].enum_oid = arg2;
+ 		mycache->sort_order_list[mycache->sort_list_length].sort_order = 
+ 			sort2;
+ 		ReleaseSysCache(enum_tup);
+ 		mycache->sort_list_length ++;
+ 		added = true;
+ 	}
+ 	else
+ 	{
+ 		/* already in cache */
+ 		sort2 = es2->sort_order;
+ 	}
+ 
+ 	if (added)
+ 	{
+ 		/* 
+ 		 * If we have more than a handful, just fetch them all, so we limit
+ 		 * the number of sort operations required.
+ 		 */
+ 		if (mycache->sort_list_length > 10 && 
+ 			mycache->sort_list_length < mycache->label_count)
+ 		{
+ 			CatCList   *nlist;
+ 			int			num, i;
+ 
+ 			nlist = SearchSysCacheList1(ENUMTYPOIDNAME, 
+ 									   ObjectIdGetDatum(mycache->enumtypoid));
+ 			num = nlist->n_members;
+ 			for (i = 0; i < num; i++)
+ 			{
+ 				HeapTuple tup = &(nlist->members[i]->tuple);
+ 				Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
+ 				
+ 				mycache->sort_order_list[i].enum_oid = HeapTupleGetOid(tup);
+ 				mycache->sort_order_list[i].sort_order = en->enumsortorder; 
+ 			}
+ 
+ 			ReleaseCatCacheList(nlist);
+ 		}
+ 
+ 		qsort(mycache->sort_order_list,mycache->sort_list_length,
+ 			  sizeof(enum_sort),enum_oid_cmp);
+ 	}
+ 
+ 	return sort1 - sort2;
+ }
+ 
  Datum
  enum_lt(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) < 0);
  }
  
  Datum
***************
*** 170,176 **** enum_le(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a <= b);
  }
  
  Datum
--- 317,323 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) <= 0);
  }
  
  Datum
***************
*** 197,203 **** enum_ge(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a >= b);
  }
  
  Datum
--- 344,350 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) >= 0);
  }
  
  Datum
***************
*** 206,212 **** enum_gt(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a > b);
  }
  
  Datum
--- 353,359 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) > 0);
  }
  
  Datum
***************
*** 215,221 **** enum_smaller(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(a <= b ? a : b);
  }
  
  Datum
--- 362,368 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(enum_ccmp(a,b,fcinfo) < 0 ? a : b);
  }
  
  Datum
***************
*** 224,230 **** enum_larger(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(a >= b ? a : b);
  }
  
  Datum
--- 371,377 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(enum_ccmp(a,b,fcinfo) > 0 ? a : b);
  }
  
  Datum
***************
*** 233,242 **** 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)
  		PG_RETURN_INT32(0);
  	else
  		PG_RETURN_INT32(-1);
  }
--- 380,389 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	if (a == b)
  		PG_RETURN_INT32(0);
+ 	else if (enum_ccmp(a,b,fcinfo) > 0)
+ 		PG_RETURN_INT32(1);
  	else
  		PG_RETURN_INT32(-1);
  }
***************
*** 248,253 **** enum_first(PG_FUNCTION_ARGS)
--- 395,401 ----
  {
  	Oid			enumtypoid;
  	Oid			min = InvalidOid;
+ 	int         min_sort = -1; /* value will never in fact be used */
  	CatCList   *list;
  	int			num,
  				i;
***************
*** 267,276 **** enum_first(PG_FUNCTION_ARGS)
  	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);
--- 415,428 ----
  	num = list->n_members;
  	for (i = 0; i < num; i++)
  	{
! 		HeapTuple tup = &(list->members[i]->tuple);
! 		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
! 
! 		if (!OidIsValid(min) || en->enumsortorder < min_sort)
! 		{
! 			min = HeapTupleGetOid(tup);
! 			min_sort = en->enumsortorder;
! 		}
  	}
  
  	ReleaseCatCacheList(list);
***************
*** 287,292 **** enum_last(PG_FUNCTION_ARGS)
--- 439,445 ----
  {
  	Oid			enumtypoid;
  	Oid			max = InvalidOid;
+ 	int         max_sort = -1; /* value will never in fact be used */
  	CatCList   *list;
  	int			num,
  				i;
***************
*** 306,315 **** enum_last(PG_FUNCTION_ARGS)
  	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);
--- 459,472 ----
  	num = list->n_members;
  	for (i = 0; i < num; i++)
  	{
! 		HeapTuple tup = &(list->members[i]->tuple);
! 		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
! 
! 		if (!OidIsValid(max) || en->enumsortorder > max_sort)
! 		{
! 			max = HeapTupleGetOid(tup);
! 			max_sort = en->enumsortorder;
! 		}
  	}
  
  	ReleaseCatCacheList(list);
***************
*** 382,427 **** enum_range_internal(Oid enumtypoid, Oid lower, Oid upper)
  				i,
  				j;
  	Datum	   *elems;
  
  	list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid));
  	total = list->n_members;
  
  	elems = (Datum *) palloc(total * sizeof(Datum));
  
- 	j = 0;
  	for (i = 0; i < total; i++)
  	{
! 		Oid			val = HeapTupleGetOid(&(list->members[i]->tuple));
  
- 		if ((!OidIsValid(lower) || lower <= val) &&
- 			(!OidIsValid(upper) || val <= upper))
- 			elems[j++] = ObjectIdGetDatum(val);
  	}
  
  	/* shouldn't need the cache anymore */
  	ReleaseCatCacheList(list);
  
! 	/* sort results into OID order */
! 	qsort(elems, j, sizeof(Datum), enum_elem_cmp);
  
  	/* note this hardwires some details about the representation of Oid */
  	result = construct_array(elems, j, 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;
  }
--- 539,614 ----
  				i,
  				j;
  	Datum	   *elems;
+ 	enum_sort  *sort_items;
+ 	bool        left_found;
  
  	list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid));
  	total = list->n_members;
  
  	elems = (Datum *) palloc(total * sizeof(Datum));
+ 	sort_items = (enum_sort *) palloc(total * sizeof(enum_sort));
  
  	for (i = 0; i < total; i++)
  	{
! 		HeapTuple tup = &(list->members[i]->tuple);
! 		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
! 
! 		sort_items[i].enum_oid = HeapTupleGetOid(tup);
! 		sort_items[i].sort_order = en->enumsortorder;
  
  	}
  
  	/* shouldn't need the cache anymore */
  	ReleaseCatCacheList(list);
  
! 	/* sort results into sort_order sequence */
! 	qsort(sort_items, total, sizeof(enum_sort), enum_sort_cmp);
! 
! 	j = 0; 
! 	left_found = !OidIsValid(lower);
! 	for (i=0; i < total; i++)
! 	{
! 		if (! left_found && lower == sort_items[i].enum_oid)
! 			left_found = true;
! 
! 		if (left_found)
! 			elems[j++] = ObjectIdGetDatum(sort_items[i].enum_oid);
! 
! 		if (OidIsValid(upper) && upper == sort_items[i].enum_oid)
! 			break;
! 	}
  
  	/* note this hardwires some details about the representation of Oid */
  	result = construct_array(elems, j, enumtypoid, sizeof(Oid), true, 'i');
  
  	pfree(elems);
+ 	pfree(sort_items);
  
  	return result;
  }
  
! /* 
!  * qsort comparison using sort order, for range routines 
!  */
  static int
! enum_sort_cmp(const void *left, const void *right)
  {
! 	enum_sort  *l = (enum_sort *) left;
! 	enum_sort  *r = (enum_sort *) right;
! 
! 	return l->sort_order - r->sort_order;
  }
+ 
+ /* 
+  * qsort comparison using OID order for comparison search cache
+  */
+ static int 
+ enum_oid_cmp(const void *es1, const void *es2)
+ {
+ 	enum_sort *p1, *p2;
+ 	p1 = (enum_sort *)es1;
+ 	p2 = (enum_sort *)es2;
+ 	return p1->enum_oid - p2->enum_oid;
+ }
+ 
+ 
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 6653,6669 **** dumpEnumType(Archive *fout, TypeInfo *tyinfo)
  	PQExpBuffer query = createPQExpBuffer();
  	PGresult   *res;
  	int			num,
! 				i;
  	Oid			enum_oid;
  	char	   *label;
  
  	/* Set proper schema search path so regproc references list correctly */
  	selectSourceSchema(tyinfo->dobj.namespace->dobj.name);
  
! 	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);
--- 6653,6676 ----
  	PQExpBuffer query = createPQExpBuffer();
  	PGresult   *res;
  	int			num,
! 		        i;
  	Oid			enum_oid;
  	char	   *label;
  
  	/* Set proper schema search path so regproc references list correctly */
  	selectSourceSchema(tyinfo->dobj.namespace->dobj.name);
  
!     if  (fout->remoteVersion > 90000)
! 		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);
***************
*** 6709,6725 **** dumpEnumType(Archive *fout, TypeInfo *tyinfo)
  		{
  			enum_oid = atooid(PQgetvalue(res, i, PQfnumber(res, "oid")));
  			label = PQgetvalue(res, i, PQfnumber(res, "enumlabel"));
! 
  			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);
! 			appendStringLiteralAH(q, label, fout);
! 			appendPQExpBuffer(q, ");\n");
  		}
- 		appendPQExpBuffer(q, "\n");
  	}
  
  	ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId,
--- 6716,6734 ----
  		{
  			enum_oid = atooid(PQgetvalue(res, i, PQfnumber(res, "oid")));
  			label = PQgetvalue(res, i, PQfnumber(res, "enumlabel"));
! 			
  			if (i == 0)
  				appendPQExpBuffer(q, "\n-- For binary upgrade, must preserve pg_enum oids\n");
  			appendPQExpBuffer(q,
! 							  "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\n");
  		}
  	}
  
  	ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId,
*** a/src/include/catalog/indexing.h
--- b/src/include/catalog/indexing.h
***************
*** 147,152 **** DECLARE_UNIQUE_INDEX(pg_enum_oid_index, 3502, on pg_enum using btree(oid oid_ops
--- 147,154 ----
  #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, 3539, on pg_enum using btree(enumtypid oid_ops, enumsortorder int4_ops));
+ #define EnumTypIdSortOrderIndexId 3539
  
  /* 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));
*** a/src/include/catalog/pg_class.h
--- b/src/include/catalog/pg_class.h
***************
*** 132,138 **** typedef FormData_pg_class *Form_pg_class;
   */
  
  /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
! DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f f r 28 0 t f f f f f 3 _null_ _null_ ));
  DESCR("");
  DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f f r 19 0 f f f f f f 3 _null_ _null_ ));
  DESCR("");
--- 132,138 ----
   */
  
  /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
! DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f f r 30 0 t f f f f f 3 _null_ _null_ ));
  DESCR("");
  DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f f r 19 0 f f f f f f 3 _null_ _null_ ));
  DESCR("");
*** a/src/include/catalog/pg_enum.h
--- b/src/include/catalog/pg_enum.h
***************
*** 35,40 **** CATALOG(pg_enum,3501)
--- 35,41 ----
  {
  	Oid			enumtypid;		/* OID of owning enum type */
  	NameData	enumlabel;		/* text representation of enum value */
+ 	int4        enumsortorder;  /* sort order for this enum label */
  } FormData_pg_enum;
  
  /* ----------------
***************
*** 48,56 **** typedef FormData_pg_enum *Form_pg_enum;
   *		compiler constants for pg_enum
   * ----------------
   */
! #define Natts_pg_enum					2
  #define Anum_pg_enum_enumtypid			1
  #define Anum_pg_enum_enumlabel			2
  
  /* ----------------
   *		pg_enum has no initial contents
--- 49,58 ----
   *		compiler constants for pg_enum
   * ----------------
   */
! #define Natts_pg_enum					3
  #define Anum_pg_enum_enumtypid			1
  #define Anum_pg_enum_enumlabel			2
+ #define Anum_pg_enum_enumsortorder		3
  
  /* ----------------
   *		pg_enum has no initial contents
***************
*** 60,67 **** 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 EnumValuesDelete(Oid enumTypeOid);
  
  #endif   /* PG_ENUM_H */
--- 62,70 ----
  /*
   * prototypes for functions in pg_enum.c
   */
! extern void EnumValuesCreate(Oid enumTypeOid, List *vals);
  extern void EnumValuesDelete(Oid enumTypeOid);
+ extern bool AddEnumLabel(Oid enumTypeOid, char *newVal, char *neighbour, 
+ 						 bool newValIsAfter, int nelems, bool elems_are_sorted);
  
  #endif   /* PG_ENUM_H */
*** a/src/include/catalog/pg_type.h
--- b/src/include/catalog/pg_type.h
***************
*** 194,199 **** CATALOG(pg_type,1247) BKI_BOOTSTRAP BKI_ROWTYPE_OID(71) BKI_SCHEMA_MACRO
--- 194,211 ----
  	 */
  	int4		typndims;
  
+     /* 
+      * typnlabels, contains a count on the number of labels an enum type has, 
+      * -1 for a non-enum type.
+      */
+     int4          typnlabels;
+ 
+     /*
+      * typsorted is true if the oids of an enum type reflect the type's sort
+      * order, false otherwise including for a non-enum type.
+      */
+     bool         typsorted;
+ 
  	/*
  	 * If typdefaultbin is not NULL, it is the nodeToString representation of
  	 * a default expression for the type.  Currently this is only used for
***************
*** 224,230 **** typedef FormData_pg_type *Form_pg_type;
   *		compiler constants for pg_type
   * ----------------
   */
! #define Natts_pg_type					28
  #define Anum_pg_type_typname			1
  #define Anum_pg_type_typnamespace		2
  #define Anum_pg_type_typowner			3
--- 236,242 ----
   *		compiler constants for pg_type
   * ----------------
   */
! #define Natts_pg_type					30
  #define Anum_pg_type_typname			1
  #define Anum_pg_type_typnamespace		2
  #define Anum_pg_type_typowner			3
***************
*** 251,258 **** typedef FormData_pg_type *Form_pg_type;
  #define Anum_pg_type_typbasetype		24
  #define Anum_pg_type_typtypmod			25
  #define Anum_pg_type_typndims			26
! #define Anum_pg_type_typdefaultbin		27
! #define Anum_pg_type_typdefault			28
  
  
  /* ----------------
--- 263,272 ----
  #define Anum_pg_type_typbasetype		24
  #define Anum_pg_type_typtypmod			25
  #define Anum_pg_type_typndims			26
! #define Anum_pg_type_typnlabels			27
! #define Anum_pg_type_typsorted			28
! #define Anum_pg_type_typdefaultbin		29
! #define Anum_pg_type_typdefault			30
  
  
  /* ----------------
***************
*** 269,355 **** typedef FormData_pg_type *Form_pg_type;
   */
  
  /* OIDS 1 - 99 */
! DATA(insert OID = 16 (	bool	   PGNSP PGUID	1 t b B t t \054 0	 0 1000 boolin boolout boolrecv boolsend - - - c p f 0 -1 0 _null_ _null_ ));
  DESCR("boolean, 'true'/'false'");
  #define BOOLOID			16
  
! DATA(insert OID = 17 (	bytea	   PGNSP PGUID -1 f b U f t \054 0	0 1001 byteain byteaout bytearecv byteasend - - - i x f 0 -1 0 _null_ _null_ ));
  DESCR("variable-length string, binary values escaped");
  #define BYTEAOID		17
  
! DATA(insert OID = 18 (	char	   PGNSP PGUID	1 t b S f t \054 0	 0 1002 charin charout charrecv charsend - - - c p f 0 -1 0 _null_ _null_ ));
  DESCR("single character");
  #define CHAROID			18
  
! DATA(insert OID = 19 (	name	   PGNSP PGUID NAMEDATALEN f b S f t \054 0 18 1003 namein nameout namerecv namesend - - - c p f 0 -1 0 _null_ _null_ ));
  DESCR("63-character type for storing system identifiers");
  #define NAMEOID			19
  
! DATA(insert OID = 20 (	int8	   PGNSP PGUID	8 FLOAT8PASSBYVAL b N f t \054 0	 0 1016 int8in int8out int8recv int8send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("~18 digit integer, 8-byte storage");
  #define INT8OID			20
  
! DATA(insert OID = 21 (	int2	   PGNSP PGUID	2 t b N f t \054 0	 0 1005 int2in int2out int2recv int2send - - - s p f 0 -1 0 _null_ _null_ ));
  DESCR("-32 thousand to 32 thousand, 2-byte storage");
  #define INT2OID			21
  
! DATA(insert OID = 22 (	int2vector PGNSP PGUID -1 f b A f t \054 0	21 1006 int2vectorin int2vectorout int2vectorrecv int2vectorsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("array of int2, used in system tables");
  #define INT2VECTOROID	22
  
! DATA(insert OID = 23 (	int4	   PGNSP PGUID	4 t b N f t \054 0	 0 1007 int4in int4out int4recv int4send - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("-2 billion to 2 billion integer, 4-byte storage");
  #define INT4OID			23
  
! DATA(insert OID = 24 (	regproc    PGNSP PGUID	4 t b N f t \054 0	 0 1008 regprocin regprocout regprocrecv regprocsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered procedure");
  #define REGPROCOID		24
  
! DATA(insert OID = 25 (	text	   PGNSP PGUID -1 f b S t t \054 0	0 1009 textin textout textrecv textsend - - - i x f 0 -1 0 _null_ _null_ ));
  DESCR("variable-length string, no limit specified");
  #define TEXTOID			25
  
! DATA(insert OID = 26 (	oid		   PGNSP PGUID	4 t b N t t \054 0	 0 1028 oidin oidout oidrecv oidsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("object identifier(oid), maximum 4 billion");
  #define OIDOID			26
  
! DATA(insert OID = 27 (	tid		   PGNSP PGUID	6 f b U f t \054 0	 0 1010 tidin tidout tidrecv tidsend - - - s p f 0 -1 0 _null_ _null_ ));
  DESCR("(block, offset), physical location of tuple");
  #define TIDOID		27
  
! DATA(insert OID = 28 (	xid		   PGNSP PGUID	4 t b U f t \054 0	 0 1011 xidin xidout xidrecv xidsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("transaction id");
  #define XIDOID 28
  
! DATA(insert OID = 29 (	cid		   PGNSP PGUID	4 t b U f t \054 0	 0 1012 cidin cidout cidrecv cidsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("command identifier type, sequence in transaction id");
  #define CIDOID 29
  
! DATA(insert OID = 30 (	oidvector  PGNSP PGUID -1 f b A f t \054 0	26 1013 oidvectorin oidvectorout oidvectorrecv oidvectorsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("array of oids, used in system tables");
  #define OIDVECTOROID	30
  
  /* hand-built rowtype entries for bootstrapped catalogs */
  /* NB: OIDs assigned here must match the BKI_ROWTYPE_OID declarations */
  
! DATA(insert OID = 71 (	pg_type			PGNSP PGUID -1 f c C f t \054 1247 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 75 (	pg_attribute	PGNSP PGUID -1 f c C f t \054 1249 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 81 (	pg_proc			PGNSP PGUID -1 f c C f t \054 1255 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 83 (	pg_class		PGNSP PGUID -1 f c C f t \054 1259 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 100 - 199 */
! DATA(insert OID = 142 ( xml		   PGNSP PGUID -1 f b U f t \054 0 0 143 xml_in xml_out xml_recv xml_send - - - i x f 0 -1 0 _null_ _null_ ));
  DESCR("XML content");
  #define XMLOID 142
! DATA(insert OID = 143 ( _xml	   PGNSP PGUID -1 f b A f t \054 0 142 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  
! DATA(insert OID = 194 (	pg_node_tree	PGNSP PGUID -1 f b S f t \054 0	0 0 pg_node_tree_in pg_node_tree_out pg_node_tree_recv pg_node_tree_send - - - i x f 0 -1 0 _null_ _null_ ));
  DESCR("string representing an internal node tree");
  #define PGNODETREEOID	194
  
  /* OIDS 200 - 299 */
  
! DATA(insert OID = 210 (  smgr	   PGNSP PGUID 2 t b U f t \054 0 0 0 smgrin smgrout - - - - - s p f 0 -1 0 _null_ _null_ ));
  DESCR("storage manager");
  
  /* OIDS 300 - 399 */
--- 283,369 ----
   */
  
  /* OIDS 1 - 99 */
! DATA(insert OID = 16 (	bool	   PGNSP PGUID	1 t b B t t \054 0	 0 1000 boolin boolout boolrecv boolsend - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("boolean, 'true'/'false'");
  #define BOOLOID			16
  
! DATA(insert OID = 17 (	bytea	   PGNSP PGUID -1 f b U f t \054 0	0 1001 byteain byteaout bytearecv byteasend - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("variable-length string, binary values escaped");
  #define BYTEAOID		17
  
! DATA(insert OID = 18 (	char	   PGNSP PGUID	1 t b S f t \054 0	 0 1002 charin charout charrecv charsend - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("single character");
  #define CHAROID			18
  
! DATA(insert OID = 19 (	name	   PGNSP PGUID NAMEDATALEN f b S f t \054 0 18 1003 namein nameout namerecv namesend - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("63-character type for storing system identifiers");
  #define NAMEOID			19
  
! DATA(insert OID = 20 (	int8	   PGNSP PGUID	8 FLOAT8PASSBYVAL b N f t \054 0	 0 1016 int8in int8out int8recv int8send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("~18 digit integer, 8-byte storage");
  #define INT8OID			20
  
! DATA(insert OID = 21 (	int2	   PGNSP PGUID	2 t b N f t \054 0	 0 1005 int2in int2out int2recv int2send - - - s p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("-32 thousand to 32 thousand, 2-byte storage");
  #define INT2OID			21
  
! DATA(insert OID = 22 (	int2vector PGNSP PGUID -1 f b A f t \054 0	21 1006 int2vectorin int2vectorout int2vectorrecv int2vectorsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("array of int2, used in system tables");
  #define INT2VECTOROID	22
  
! DATA(insert OID = 23 (	int4	   PGNSP PGUID	4 t b N f t \054 0	 0 1007 int4in int4out int4recv int4send - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("-2 billion to 2 billion integer, 4-byte storage");
  #define INT4OID			23
  
! DATA(insert OID = 24 (	regproc    PGNSP PGUID	4 t b N f t \054 0	 0 1008 regprocin regprocout regprocrecv regprocsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered procedure");
  #define REGPROCOID		24
  
! DATA(insert OID = 25 (	text	   PGNSP PGUID -1 f b S t t \054 0	0 1009 textin textout textrecv textsend - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("variable-length string, no limit specified");
  #define TEXTOID			25
  
! DATA(insert OID = 26 (	oid		   PGNSP PGUID	4 t b N t t \054 0	 0 1028 oidin oidout oidrecv oidsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("object identifier(oid), maximum 4 billion");
  #define OIDOID			26
  
! DATA(insert OID = 27 (	tid		   PGNSP PGUID	6 f b U f t \054 0	 0 1010 tidin tidout tidrecv tidsend - - - s p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("(block, offset), physical location of tuple");
  #define TIDOID		27
  
! DATA(insert OID = 28 (	xid		   PGNSP PGUID	4 t b U f t \054 0	 0 1011 xidin xidout xidrecv xidsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("transaction id");
  #define XIDOID 28
  
! DATA(insert OID = 29 (	cid		   PGNSP PGUID	4 t b U f t \054 0	 0 1012 cidin cidout cidrecv cidsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("command identifier type, sequence in transaction id");
  #define CIDOID 29
  
! DATA(insert OID = 30 (	oidvector  PGNSP PGUID -1 f b A f t \054 0	26 1013 oidvectorin oidvectorout oidvectorrecv oidvectorsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("array of oids, used in system tables");
  #define OIDVECTOROID	30
  
  /* hand-built rowtype entries for bootstrapped catalogs */
  /* NB: OIDs assigned here must match the BKI_ROWTYPE_OID declarations */
  
! DATA(insert OID = 71 (	pg_type			PGNSP PGUID -1 f c C f t \054 1247 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 75 (	pg_attribute	PGNSP PGUID -1 f c C f t \054 1249 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 81 (	pg_proc			PGNSP PGUID -1 f c C f t \054 1255 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 83 (	pg_class		PGNSP PGUID -1 f c C f t \054 1259 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 100 - 199 */
! DATA(insert OID = 142 ( xml		   PGNSP PGUID -1 f b U f t \054 0 0 143 xml_in xml_out xml_recv xml_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("XML content");
  #define XMLOID 142
! DATA(insert OID = 143 ( _xml	   PGNSP PGUID -1 f b A f t \054 0 142 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  
! DATA(insert OID = 194 (	pg_node_tree	PGNSP PGUID -1 f b S f t \054 0	0 0 pg_node_tree_in pg_node_tree_out pg_node_tree_recv pg_node_tree_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("string representing an internal node tree");
  #define PGNODETREEOID	194
  
  /* OIDS 200 - 299 */
  
! DATA(insert OID = 210 (  smgr	   PGNSP PGUID 2 t b U f t \054 0 0 0 smgrin smgrout - - - - - s p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("storage manager");
  
  /* OIDS 300 - 399 */
***************
*** 359,589 **** DESCR("storage manager");
  /* OIDS 500 - 599 */
  
  /* OIDS 600 - 699 */
! DATA(insert OID = 600 (  point	   PGNSP PGUID 16 f b G f t \054 0 701 1017 point_in point_out point_recv point_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("geometric point '(x, y)'");
  #define POINTOID		600
! DATA(insert OID = 601 (  lseg	   PGNSP PGUID 32 f b G f t \054 0 600 1018 lseg_in lseg_out lseg_recv lseg_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("geometric line segment '(pt1,pt2)'");
  #define LSEGOID			601
! DATA(insert OID = 602 (  path	   PGNSP PGUID -1 f b G f t \054 0 0 1019 path_in path_out path_recv path_send - - - d x f 0 -1 0 _null_ _null_ ));
  DESCR("geometric path '(pt1,...)'");
  #define PATHOID			602
! DATA(insert OID = 603 (  box	   PGNSP PGUID 32 f b G f t \073 0 600 1020 box_in box_out box_recv box_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("geometric box '(lower left,upper right)'");
  #define BOXOID			603
! DATA(insert OID = 604 (  polygon   PGNSP PGUID -1 f b G f t \054 0	 0 1027 poly_in poly_out poly_recv poly_send - - - d x f 0 -1 0 _null_ _null_ ));
  DESCR("geometric polygon '(pt1,...)'");
  #define POLYGONOID		604
  
! DATA(insert OID = 628 (  line	   PGNSP PGUID 32 f b G f t \054 0 701 629 line_in line_out line_recv line_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("geometric line (not implemented)");
  #define LINEOID			628
! DATA(insert OID = 629 (  _line	   PGNSP PGUID	-1 f b A f t \054 0 628 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
  DESCR("");
  
  /* OIDS 700 - 799 */
  
! DATA(insert OID = 700 (  float4    PGNSP PGUID	4 FLOAT4PASSBYVAL b N f t \054 0	 0 1021 float4in float4out float4recv float4send - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("single-precision floating point number, 4-byte storage");
  #define FLOAT4OID 700
! DATA(insert OID = 701 (  float8    PGNSP PGUID	8 FLOAT8PASSBYVAL b N t t \054 0	 0 1022 float8in float8out float8recv float8send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("double-precision floating point number, 8-byte storage");
  #define FLOAT8OID 701
! DATA(insert OID = 702 (  abstime   PGNSP PGUID	4 t b D f t \054 0	 0 1023 abstimein abstimeout abstimerecv abstimesend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("absolute, limited-range date and time (Unix system time)");
  #define ABSTIMEOID		702
! DATA(insert OID = 703 (  reltime   PGNSP PGUID	4 t b T f t \054 0	 0 1024 reltimein reltimeout reltimerecv reltimesend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("relative, limited-range time interval (Unix delta time)");
  #define RELTIMEOID		703
! DATA(insert OID = 704 (  tinterval PGNSP PGUID 12 f b T f t \054 0	 0 1025 tintervalin tintervalout tintervalrecv tintervalsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("(abstime,abstime), time interval");
  #define TINTERVALOID	704
! DATA(insert OID = 705 (  unknown   PGNSP PGUID -2 f b X f t \054 0	 0 0 unknownin unknownout unknownrecv unknownsend - - - c p f 0 -1 0 _null_ _null_ ));
  DESCR("");
  #define UNKNOWNOID		705
  
! DATA(insert OID = 718 (  circle    PGNSP PGUID	24 f b G f t \054 0 0 719 circle_in circle_out circle_recv circle_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("geometric circle '(center,radius)'");
  #define CIRCLEOID		718
! DATA(insert OID = 719 (  _circle   PGNSP PGUID	-1 f b A f t \054 0  718 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 790 (  money	   PGNSP PGUID	 8 FLOAT8PASSBYVAL b N f t \054 0 0 791 cash_in cash_out cash_recv cash_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("monetary amounts, $d,ddd.cc");
  #define CASHOID 790
! DATA(insert OID = 791 (  _money    PGNSP PGUID	-1 f b A f t \054 0  790 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 800 - 899 */
! DATA(insert OID = 829 ( macaddr    PGNSP PGUID	6 f b U f t \054 0 0 1040 macaddr_in macaddr_out macaddr_recv macaddr_send - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("XX:XX:XX:XX:XX:XX, MAC address");
  #define MACADDROID 829
! DATA(insert OID = 869 ( inet	   PGNSP PGUID	-1 f b I t t \054 0 0 1041 inet_in inet_out inet_recv inet_send - - - i m f 0 -1 0 _null_ _null_ ));
  DESCR("IP address/netmask, host address, netmask optional");
  #define INETOID 869
! DATA(insert OID = 650 ( cidr	   PGNSP PGUID	-1 f b I f t \054 0 0 651 cidr_in cidr_out cidr_recv cidr_send - - - i m f 0 -1 0 _null_ _null_ ));
  DESCR("network IP address/netmask, network address");
  #define CIDROID 650
  
  /* OIDS 900 - 999 */
  
  /* OIDS 1000 - 1099 */
! DATA(insert OID = 1000 (  _bool		 PGNSP PGUID -1 f b A f t \054 0	16 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1001 (  _bytea	 PGNSP PGUID -1 f b A f t \054 0	17 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1002 (  _char		 PGNSP PGUID -1 f b A f t \054 0	18 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1003 (  _name		 PGNSP PGUID -1 f b A f t \054 0	19 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1005 (  _int2		 PGNSP PGUID -1 f b A f t \054 0	21 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1006 (  _int2vector PGNSP PGUID -1 f b A f t \054 0	22 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1007 (  _int4		 PGNSP PGUID -1 f b A f t \054 0	23 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  #define INT4ARRAYOID		1007
! DATA(insert OID = 1008 (  _regproc	 PGNSP PGUID -1 f b A f t \054 0	24 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1009 (  _text		 PGNSP PGUID -1 f b A f t \054 0	25 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  #define TEXTARRAYOID		1009
! DATA(insert OID = 1028 (  _oid		 PGNSP PGUID -1 f b A f t \054 0	26 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1010 (  _tid		 PGNSP PGUID -1 f b A f t \054 0	27 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1011 (  _xid		 PGNSP PGUID -1 f b A f t \054 0	28 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1012 (  _cid		 PGNSP PGUID -1 f b A f t \054 0	29 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1013 (  _oidvector PGNSP PGUID -1 f b A f t \054 0	30 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1014 (  _bpchar	 PGNSP PGUID -1 f b A f t \054 0 1042 0 array_in array_out array_recv array_send bpchartypmodin bpchartypmodout - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1015 (  _varchar	 PGNSP PGUID -1 f b A f t \054 0 1043 0 array_in array_out array_recv array_send varchartypmodin varchartypmodout - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1016 (  _int8		 PGNSP PGUID -1 f b A f t \054 0	20 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1017 (  _point	 PGNSP PGUID -1 f b A f t \054 0 600 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1018 (  _lseg		 PGNSP PGUID -1 f b A f t \054 0 601 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1019 (  _path		 PGNSP PGUID -1 f b A f t \054 0 602 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1020 (  _box		 PGNSP PGUID -1 f b A f t \073 0 603 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1021 (  _float4	 PGNSP PGUID -1 f b A f t \054 0 700 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  #define FLOAT4ARRAYOID 1021
! DATA(insert OID = 1022 (  _float8	 PGNSP PGUID -1 f b A f t \054 0 701 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1023 (  _abstime	 PGNSP PGUID -1 f b A f t \054 0 702 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1024 (  _reltime	 PGNSP PGUID -1 f b A f t \054 0 703 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1025 (  _tinterval PGNSP PGUID -1 f b A f t \054 0 704 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1027 (  _polygon	 PGNSP PGUID -1 f b A f t \054 0 604 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1033 (  aclitem	 PGNSP PGUID 12 f b U f t \054 0 0 1034 aclitemin aclitemout - - - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("access control list");
  #define ACLITEMOID		1033
! DATA(insert OID = 1034 (  _aclitem	 PGNSP PGUID -1 f b A f t \054 0 1033 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1040 (  _macaddr	 PGNSP PGUID -1 f b A f t \054 0  829 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1041 (  _inet		 PGNSP PGUID -1 f b A f t \054 0  869 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 651  (  _cidr		 PGNSP PGUID -1 f b A f t \054 0  650 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1263 (  _cstring	 PGNSP PGUID -1 f b A f t \054 0 2275 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  #define CSTRINGARRAYOID		1263
  
! DATA(insert OID = 1042 ( bpchar		 PGNSP PGUID -1 f b S f t \054 0	0 1014 bpcharin bpcharout bpcharrecv bpcharsend bpchartypmodin bpchartypmodout - i x f 0 -1 0 _null_ _null_ ));
  DESCR("char(length), blank-padded string, fixed storage length");
  #define BPCHAROID		1042
! DATA(insert OID = 1043 ( varchar	 PGNSP PGUID -1 f b S f t \054 0	0 1015 varcharin varcharout varcharrecv varcharsend varchartypmodin varchartypmodout - i x f 0 -1 0 _null_ _null_ ));
  DESCR("varchar(length), non-blank-padded string, variable storage length");
  #define VARCHAROID		1043
  
! DATA(insert OID = 1082 ( date		 PGNSP PGUID	4 t b D f t \054 0	0 1182 date_in date_out date_recv date_send - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("date");
  #define DATEOID			1082
! DATA(insert OID = 1083 ( time		 PGNSP PGUID	8 FLOAT8PASSBYVAL b D f t \054 0	0 1183 time_in time_out time_recv time_send timetypmodin timetypmodout - d p f 0 -1 0 _null_ _null_ ));
  DESCR("time of day");
  #define TIMEOID			1083
  
  /* OIDS 1100 - 1199 */
! DATA(insert OID = 1114 ( timestamp	 PGNSP PGUID	8 FLOAT8PASSBYVAL b D f t \054 0	0 1115 timestamp_in timestamp_out timestamp_recv timestamp_send timestamptypmodin timestamptypmodout - d p f 0 -1 0 _null_ _null_ ));
  DESCR("date and time");
  #define TIMESTAMPOID	1114
! DATA(insert OID = 1115 ( _timestamp  PGNSP PGUID	-1 f b A f t \054 0 1114 0 array_in array_out array_recv array_send timestamptypmodin timestamptypmodout - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1182 ( _date		 PGNSP PGUID	-1 f b A f t \054 0 1082 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1183 ( _time		 PGNSP PGUID	-1 f b A f t \054 0 1083 0 array_in array_out array_recv array_send timetypmodin timetypmodout - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1184 ( timestamptz PGNSP PGUID	8 FLOAT8PASSBYVAL b D t t \054 0	0 1185 timestamptz_in timestamptz_out timestamptz_recv timestamptz_send timestamptztypmodin timestamptztypmodout - d p f 0 -1 0 _null_ _null_ ));
  DESCR("date and time with time zone");
  #define TIMESTAMPTZOID	1184
! DATA(insert OID = 1185 ( _timestamptz PGNSP PGUID -1 f b A f t \054 0	1184 0 array_in array_out array_recv array_send timestamptztypmodin timestamptztypmodout - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1186 ( interval	 PGNSP PGUID 16 f b T t t \054 0	0 1187 interval_in interval_out interval_recv interval_send intervaltypmodin intervaltypmodout - d p f 0 -1 0 _null_ _null_ ));
  DESCR("@ <number> <units>, time interval");
  #define INTERVALOID		1186
! DATA(insert OID = 1187 ( _interval	 PGNSP PGUID	-1 f b A f t \054 0 1186 0 array_in array_out array_recv array_send intervaltypmodin intervaltypmodout - d x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 1200 - 1299 */
! DATA(insert OID = 1231 (  _numeric	 PGNSP PGUID -1 f b A f t \054 0	1700 0 array_in array_out array_recv array_send numerictypmodin numerictypmodout - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1266 ( timetz		 PGNSP PGUID 12 f b D f t \054 0	0 1270 timetz_in timetz_out timetz_recv timetz_send timetztypmodin timetztypmodout - d p f 0 -1 0 _null_ _null_ ));
  DESCR("time of day with time zone");
  #define TIMETZOID		1266
! DATA(insert OID = 1270 ( _timetz	 PGNSP PGUID -1 f b A f t \054 0	1266 0 array_in array_out array_recv array_send timetztypmodin timetztypmodout - d x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 1500 - 1599 */
! DATA(insert OID = 1560 ( bit		 PGNSP PGUID -1 f b V f t \054 0	0 1561 bit_in bit_out bit_recv bit_send bittypmodin bittypmodout - i x f 0 -1 0 _null_ _null_ ));
  DESCR("fixed-length bit string");
  #define BITOID	 1560
! DATA(insert OID = 1561 ( _bit		 PGNSP PGUID -1 f b A f t \054 0	1560 0 array_in array_out array_recv array_send bittypmodin bittypmodout - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1562 ( varbit		 PGNSP PGUID -1 f b V t t \054 0	0 1563 varbit_in varbit_out varbit_recv varbit_send varbittypmodin varbittypmodout - i x f 0 -1 0 _null_ _null_ ));
  DESCR("variable-length bit string");
  #define VARBITOID	  1562
! DATA(insert OID = 1563 ( _varbit	 PGNSP PGUID -1 f b A f t \054 0	1562 0 array_in array_out array_recv array_send varbittypmodin varbittypmodout - i x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 1600 - 1699 */
  
  /* OIDS 1700 - 1799 */
! DATA(insert OID = 1700 ( numeric	   PGNSP PGUID -1 f b N f t \054 0	0 1231 numeric_in numeric_out numeric_recv numeric_send numerictypmodin numerictypmodout - i m f 0 -1 0 _null_ _null_ ));
  DESCR("numeric(precision, decimal), arbitrary precision number");
  #define NUMERICOID		1700
  
! DATA(insert OID = 1790 ( refcursor	   PGNSP PGUID -1 f b U f t \054 0	0 2201 textin textout textrecv textsend - - - i x f 0 -1 0 _null_ _null_ ));
  DESCR("reference to cursor (portal name)");
  #define REFCURSOROID	1790
  
  /* OIDS 2200 - 2299 */
! DATA(insert OID = 2201 ( _refcursor    PGNSP PGUID -1 f b A f t \054 0 1790 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  
! DATA(insert OID = 2202 ( regprocedure  PGNSP PGUID	4 t b N f t \054 0	 0 2207 regprocedurein regprocedureout regprocedurerecv regproceduresend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered procedure (with args)");
  #define REGPROCEDUREOID 2202
  
! DATA(insert OID = 2203 ( regoper	   PGNSP PGUID	4 t b N f t \054 0	 0 2208 regoperin regoperout regoperrecv regopersend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered operator");
  #define REGOPEROID		2203
  
! DATA(insert OID = 2204 ( regoperator   PGNSP PGUID	4 t b N f t \054 0	 0 2209 regoperatorin regoperatorout regoperatorrecv regoperatorsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered operator (with args)");
  #define REGOPERATOROID	2204
  
! DATA(insert OID = 2205 ( regclass	   PGNSP PGUID	4 t b N f t \054 0	 0 2210 regclassin regclassout regclassrecv regclasssend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered class");
  #define REGCLASSOID		2205
  
! DATA(insert OID = 2206 ( regtype	   PGNSP PGUID	4 t b N f t \054 0	 0 2211 regtypein regtypeout regtyperecv regtypesend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered type");
  #define REGTYPEOID		2206
  
! DATA(insert OID = 2207 ( _regprocedure PGNSP PGUID -1 f b A f t \054 0 2202 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 2208 ( _regoper	   PGNSP PGUID -1 f b A f t \054 0 2203 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 2209 ( _regoperator  PGNSP PGUID -1 f b A f t \054 0 2204 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 2210 ( _regclass	   PGNSP PGUID -1 f b A f t \054 0 2205 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 2211 ( _regtype	   PGNSP PGUID -1 f b A f t \054 0 2206 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  #define REGTYPEARRAYOID 2211
  
  /* uuid */
! DATA(insert OID = 2950 ( uuid			PGNSP PGUID 16 f b U f t \054 0 0 2951 uuid_in uuid_out uuid_recv uuid_send - - - c p f 0 -1 0 _null_ _null_ ));
  DESCR("UUID datatype");
! DATA(insert OID = 2951 ( _uuid			PGNSP PGUID -1 f b A f t \054 0 2950 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  
  /* text search */
! DATA(insert OID = 3614 ( tsvector		PGNSP PGUID -1 f b U f t \054 0 0 3643 tsvectorin tsvectorout tsvectorrecv tsvectorsend - - ts_typanalyze i x f 0 -1 0 _null_ _null_ ));
  DESCR("text representation for text search");
  #define TSVECTOROID		3614
! DATA(insert OID = 3642 ( gtsvector		PGNSP PGUID -1 f b U f t \054 0 0 3644 gtsvectorin gtsvectorout - - - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("GiST index internal text representation for text search");
  #define GTSVECTOROID	3642
! DATA(insert OID = 3615 ( tsquery		PGNSP PGUID -1 f b U f t \054 0 0 3645 tsqueryin tsqueryout tsqueryrecv tsquerysend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("query representation for text search");
  #define TSQUERYOID		3615
! DATA(insert OID = 3734 ( regconfig		PGNSP PGUID 4 t b N f t \054 0 0 3735 regconfigin regconfigout regconfigrecv regconfigsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered text search configuration");
  #define REGCONFIGOID	3734
! DATA(insert OID = 3769 ( regdictionary	PGNSP PGUID 4 t b N f t \054 0 0 3770 regdictionaryin regdictionaryout regdictionaryrecv regdictionarysend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered text search dictionary");
  #define REGDICTIONARYOID	3769
  
! DATA(insert OID = 3643 ( _tsvector		PGNSP PGUID -1 f b A f t \054 0 3614 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 3644 ( _gtsvector		PGNSP PGUID -1 f b A f t \054 0 3642 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 3645 ( _tsquery		PGNSP PGUID -1 f b A f t \054 0 3615 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 3735 ( _regconfig		PGNSP PGUID -1 f b A f t \054 0 3734 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 3770 ( _regdictionary PGNSP PGUID -1 f b A f t \054 0 3769 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  
! DATA(insert OID = 2970 ( txid_snapshot	PGNSP PGUID -1 f b U f t \054 0 0 2949 txid_snapshot_in txid_snapshot_out txid_snapshot_recv txid_snapshot_send - - - d x f 0 -1 0 _null_ _null_ ));
  DESCR("txid snapshot");
! DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
  
  /*
   * pseudo-types
--- 373,603 ----
  /* OIDS 500 - 599 */
  
  /* OIDS 600 - 699 */
! DATA(insert OID = 600 (  point	   PGNSP PGUID 16 f b G f t \054 0 701 1017 point_in point_out point_recv point_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric point '(x, y)'");
  #define POINTOID		600
! DATA(insert OID = 601 (  lseg	   PGNSP PGUID 32 f b G f t \054 0 600 1018 lseg_in lseg_out lseg_recv lseg_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric line segment '(pt1,pt2)'");
  #define LSEGOID			601
! DATA(insert OID = 602 (  path	   PGNSP PGUID -1 f b G f t \054 0 0 1019 path_in path_out path_recv path_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric path '(pt1,...)'");
  #define PATHOID			602
! DATA(insert OID = 603 (  box	   PGNSP PGUID 32 f b G f t \073 0 600 1020 box_in box_out box_recv box_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric box '(lower left,upper right)'");
  #define BOXOID			603
! DATA(insert OID = 604 (  polygon   PGNSP PGUID -1 f b G f t \054 0	 0 1027 poly_in poly_out poly_recv poly_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric polygon '(pt1,...)'");
  #define POLYGONOID		604
  
! DATA(insert OID = 628 (  line	   PGNSP PGUID 32 f b G f t \054 0 701 629 line_in line_out line_recv line_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric line (not implemented)");
  #define LINEOID			628
! DATA(insert OID = 629 (  _line	   PGNSP PGUID	-1 f b A f t \054 0 628 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("");
  
  /* OIDS 700 - 799 */
  
! DATA(insert OID = 700 (  float4    PGNSP PGUID	4 FLOAT4PASSBYVAL b N f t \054 0	 0 1021 float4in float4out float4recv float4send - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("single-precision floating point number, 4-byte storage");
  #define FLOAT4OID 700
! DATA(insert OID = 701 (  float8    PGNSP PGUID	8 FLOAT8PASSBYVAL b N t t \054 0	 0 1022 float8in float8out float8recv float8send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("double-precision floating point number, 8-byte storage");
  #define FLOAT8OID 701
! DATA(insert OID = 702 (  abstime   PGNSP PGUID	4 t b D f t \054 0	 0 1023 abstimein abstimeout abstimerecv abstimesend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("absolute, limited-range date and time (Unix system time)");
  #define ABSTIMEOID		702
! DATA(insert OID = 703 (  reltime   PGNSP PGUID	4 t b T f t \054 0	 0 1024 reltimein reltimeout reltimerecv reltimesend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("relative, limited-range time interval (Unix delta time)");
  #define RELTIMEOID		703
! DATA(insert OID = 704 (  tinterval PGNSP PGUID 12 f b T f t \054 0	 0 1025 tintervalin tintervalout tintervalrecv tintervalsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("(abstime,abstime), time interval");
  #define TINTERVALOID	704
! DATA(insert OID = 705 (  unknown   PGNSP PGUID -2 f b X f t \054 0	 0 0 unknownin unknownout unknownrecv unknownsend - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("");
  #define UNKNOWNOID		705
  
! DATA(insert OID = 718 (  circle    PGNSP PGUID	24 f b G f t \054 0 0 719 circle_in circle_out circle_recv circle_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric circle '(center,radius)'");
  #define CIRCLEOID		718
! DATA(insert OID = 719 (  _circle   PGNSP PGUID	-1 f b A f t \054 0  718 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 790 (  money	   PGNSP PGUID	 8 FLOAT8PASSBYVAL b N f t \054 0 0 791 cash_in cash_out cash_recv cash_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("monetary amounts, $d,ddd.cc");
  #define CASHOID 790
! DATA(insert OID = 791 (  _money    PGNSP PGUID	-1 f b A f t \054 0  790 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 800 - 899 */
! DATA(insert OID = 829 ( macaddr    PGNSP PGUID	6 f b U f t \054 0 0 1040 macaddr_in macaddr_out macaddr_recv macaddr_send - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("XX:XX:XX:XX:XX:XX, MAC address");
  #define MACADDROID 829
! DATA(insert OID = 869 ( inet	   PGNSP PGUID	-1 f b I t t \054 0 0 1041 inet_in inet_out inet_recv inet_send - - - i m f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("IP address/netmask, host address, netmask optional");
  #define INETOID 869
! DATA(insert OID = 650 ( cidr	   PGNSP PGUID	-1 f b I f t \054 0 0 651 cidr_in cidr_out cidr_recv cidr_send - - - i m f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("network IP address/netmask, network address");
  #define CIDROID 650
  
  /* OIDS 900 - 999 */
  
  /* OIDS 1000 - 1099 */
! DATA(insert OID = 1000 (  _bool		 PGNSP PGUID -1 f b A f t \054 0	16 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1001 (  _bytea	 PGNSP PGUID -1 f b A f t \054 0	17 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1002 (  _char		 PGNSP PGUID -1 f b A f t \054 0	18 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1003 (  _name		 PGNSP PGUID -1 f b A f t \054 0	19 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1005 (  _int2		 PGNSP PGUID -1 f b A f t \054 0	21 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1006 (  _int2vector PGNSP PGUID -1 f b A f t \054 0	22 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1007 (  _int4		 PGNSP PGUID -1 f b A f t \054 0	23 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  #define INT4ARRAYOID		1007
! DATA(insert OID = 1008 (  _regproc	 PGNSP PGUID -1 f b A f t \054 0	24 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1009 (  _text		 PGNSP PGUID -1 f b A f t \054 0	25 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  #define TEXTARRAYOID		1009
! DATA(insert OID = 1028 (  _oid		 PGNSP PGUID -1 f b A f t \054 0	26 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1010 (  _tid		 PGNSP PGUID -1 f b A f t \054 0	27 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1011 (  _xid		 PGNSP PGUID -1 f b A f t \054 0	28 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1012 (  _cid		 PGNSP PGUID -1 f b A f t \054 0	29 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1013 (  _oidvector PGNSP PGUID -1 f b A f t \054 0	30 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1014 (  _bpchar	 PGNSP PGUID -1 f b A f t \054 0 1042 0 array_in array_out array_recv array_send bpchartypmodin bpchartypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1015 (  _varchar	 PGNSP PGUID -1 f b A f t \054 0 1043 0 array_in array_out array_recv array_send varchartypmodin varchartypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1016 (  _int8		 PGNSP PGUID -1 f b A f t \054 0	20 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1017 (  _point	 PGNSP PGUID -1 f b A f t \054 0 600 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1018 (  _lseg		 PGNSP PGUID -1 f b A f t \054 0 601 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1019 (  _path		 PGNSP PGUID -1 f b A f t \054 0 602 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1020 (  _box		 PGNSP PGUID -1 f b A f t \073 0 603 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1021 (  _float4	 PGNSP PGUID -1 f b A f t \054 0 700 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  #define FLOAT4ARRAYOID 1021
! DATA(insert OID = 1022 (  _float8	 PGNSP PGUID -1 f b A f t \054 0 701 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1023 (  _abstime	 PGNSP PGUID -1 f b A f t \054 0 702 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1024 (  _reltime	 PGNSP PGUID -1 f b A f t \054 0 703 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1025 (  _tinterval PGNSP PGUID -1 f b A f t \054 0 704 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1027 (  _polygon	 PGNSP PGUID -1 f b A f t \054 0 604 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1033 (  aclitem	 PGNSP PGUID 12 f b U f t \054 0 0 1034 aclitemin aclitemout - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("access control list");
  #define ACLITEMOID		1033
! DATA(insert OID = 1034 (  _aclitem	 PGNSP PGUID -1 f b A f t \054 0 1033 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1040 (  _macaddr	 PGNSP PGUID -1 f b A f t \054 0  829 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1041 (  _inet		 PGNSP PGUID -1 f b A f t \054 0  869 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 651  (  _cidr		 PGNSP PGUID -1 f b A f t \054 0  650 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1263 (  _cstring	 PGNSP PGUID -1 f b A f t \054 0 2275 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  #define CSTRINGARRAYOID		1263
  
! DATA(insert OID = 1042 ( bpchar		 PGNSP PGUID -1 f b S f t \054 0	0 1014 bpcharin bpcharout bpcharrecv bpcharsend bpchartypmodin bpchartypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("char(length), blank-padded string, fixed storage length");
  #define BPCHAROID		1042
! DATA(insert OID = 1043 ( varchar	 PGNSP PGUID -1 f b S f t \054 0	0 1015 varcharin varcharout varcharrecv varcharsend varchartypmodin varchartypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("varchar(length), non-blank-padded string, variable storage length");
  #define VARCHAROID		1043
  
! DATA(insert OID = 1082 ( date		 PGNSP PGUID	4 t b D f t \054 0	0 1182 date_in date_out date_recv date_send - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("date");
  #define DATEOID			1082
! DATA(insert OID = 1083 ( time		 PGNSP PGUID	8 FLOAT8PASSBYVAL b D f t \054 0	0 1183 time_in time_out time_recv time_send timetypmodin timetypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("time of day");
  #define TIMEOID			1083
  
  /* OIDS 1100 - 1199 */
! DATA(insert OID = 1114 ( timestamp	 PGNSP PGUID	8 FLOAT8PASSBYVAL b D f t \054 0	0 1115 timestamp_in timestamp_out timestamp_recv timestamp_send timestamptypmodin timestamptypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("date and time");
  #define TIMESTAMPOID	1114
! DATA(insert OID = 1115 ( _timestamp  PGNSP PGUID	-1 f b A f t \054 0 1114 0 array_in array_out array_recv array_send timestamptypmodin timestamptypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1182 ( _date		 PGNSP PGUID	-1 f b A f t \054 0 1082 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1183 ( _time		 PGNSP PGUID	-1 f b A f t \054 0 1083 0 array_in array_out array_recv array_send timetypmodin timetypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1184 ( timestamptz PGNSP PGUID	8 FLOAT8PASSBYVAL b D t t \054 0	0 1185 timestamptz_in timestamptz_out timestamptz_recv timestamptz_send timestamptztypmodin timestamptztypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("date and time with time zone");
  #define TIMESTAMPTZOID	1184
! DATA(insert OID = 1185 ( _timestamptz PGNSP PGUID -1 f b A f t \054 0	1184 0 array_in array_out array_recv array_send timestamptztypmodin timestamptztypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1186 ( interval	 PGNSP PGUID 16 f b T t t \054 0	0 1187 interval_in interval_out interval_recv interval_send intervaltypmodin intervaltypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("@ <number> <units>, time interval");
  #define INTERVALOID		1186
! DATA(insert OID = 1187 ( _interval	 PGNSP PGUID	-1 f b A f t \054 0 1186 0 array_in array_out array_recv array_send intervaltypmodin intervaltypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 1200 - 1299 */
! DATA(insert OID = 1231 (  _numeric	 PGNSP PGUID -1 f b A f t \054 0	1700 0 array_in array_out array_recv array_send numerictypmodin numerictypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1266 ( timetz		 PGNSP PGUID 12 f b D f t \054 0	0 1270 timetz_in timetz_out timetz_recv timetz_send timetztypmodin timetztypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("time of day with time zone");
  #define TIMETZOID		1266
! DATA(insert OID = 1270 ( _timetz	 PGNSP PGUID -1 f b A f t \054 0	1266 0 array_in array_out array_recv array_send timetztypmodin timetztypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 1500 - 1599 */
! DATA(insert OID = 1560 ( bit		 PGNSP PGUID -1 f b V f t \054 0	0 1561 bit_in bit_out bit_recv bit_send bittypmodin bittypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("fixed-length bit string");
  #define BITOID	 1560
! DATA(insert OID = 1561 ( _bit		 PGNSP PGUID -1 f b A f t \054 0	1560 0 array_in array_out array_recv array_send bittypmodin bittypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1562 ( varbit		 PGNSP PGUID -1 f b V t t \054 0	0 1563 varbit_in varbit_out varbit_recv varbit_send varbittypmodin varbittypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("variable-length bit string");
  #define VARBITOID	  1562
! DATA(insert OID = 1563 ( _varbit	 PGNSP PGUID -1 f b A f t \054 0	1562 0 array_in array_out array_recv array_send varbittypmodin varbittypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 1600 - 1699 */
  
  /* OIDS 1700 - 1799 */
! DATA(insert OID = 1700 ( numeric	   PGNSP PGUID -1 f b N f t \054 0	0 1231 numeric_in numeric_out numeric_recv numeric_send numerictypmodin numerictypmodout - i m f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("numeric(precision, decimal), arbitrary precision number");
  #define NUMERICOID		1700
  
! DATA(insert OID = 1790 ( refcursor	   PGNSP PGUID -1 f b U f t \054 0	0 2201 textin textout textrecv textsend - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("reference to cursor (portal name)");
  #define REFCURSOROID	1790
  
  /* OIDS 2200 - 2299 */
! DATA(insert OID = 2201 ( _refcursor    PGNSP PGUID -1 f b A f t \054 0 1790 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  
! DATA(insert OID = 2202 ( regprocedure  PGNSP PGUID	4 t b N f t \054 0	 0 2207 regprocedurein regprocedureout regprocedurerecv regproceduresend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered procedure (with args)");
  #define REGPROCEDUREOID 2202
  
! DATA(insert OID = 2203 ( regoper	   PGNSP PGUID	4 t b N f t \054 0	 0 2208 regoperin regoperout regoperrecv regopersend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered operator");
  #define REGOPEROID		2203
  
! DATA(insert OID = 2204 ( regoperator   PGNSP PGUID	4 t b N f t \054 0	 0 2209 regoperatorin regoperatorout regoperatorrecv regoperatorsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered operator (with args)");
  #define REGOPERATOROID	2204
  
! DATA(insert OID = 2205 ( regclass	   PGNSP PGUID	4 t b N f t \054 0	 0 2210 regclassin regclassout regclassrecv regclasssend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered class");
  #define REGCLASSOID		2205
  
! DATA(insert OID = 2206 ( regtype	   PGNSP PGUID	4 t b N f t \054 0	 0 2211 regtypein regtypeout regtyperecv regtypesend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered type");
  #define REGTYPEOID		2206
  
! DATA(insert OID = 2207 ( _regprocedure PGNSP PGUID -1 f b A f t \054 0 2202 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 2208 ( _regoper	   PGNSP PGUID -1 f b A f t \054 0 2203 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 2209 ( _regoperator  PGNSP PGUID -1 f b A f t \054 0 2204 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 2210 ( _regclass	   PGNSP PGUID -1 f b A f t \054 0 2205 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 2211 ( _regtype	   PGNSP PGUID -1 f b A f t \054 0 2206 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  #define REGTYPEARRAYOID 2211
  
  /* uuid */
! DATA(insert OID = 2950 ( uuid			PGNSP PGUID 16 f b U f t \054 0 0 2951 uuid_in uuid_out uuid_recv uuid_send - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("UUID datatype");
! DATA(insert OID = 2951 ( _uuid			PGNSP PGUID -1 f b A f t \054 0 2950 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* text search */
! DATA(insert OID = 3614 ( tsvector		PGNSP PGUID -1 f b U f t \054 0 0 3643 tsvectorin tsvectorout tsvectorrecv tsvectorsend - - ts_typanalyze i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("text representation for text search");
  #define TSVECTOROID		3614
! DATA(insert OID = 3642 ( gtsvector		PGNSP PGUID -1 f b U f t \054 0 0 3644 gtsvectorin gtsvectorout - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("GiST index internal text representation for text search");
  #define GTSVECTOROID	3642
! DATA(insert OID = 3615 ( tsquery		PGNSP PGUID -1 f b U f t \054 0 0 3645 tsqueryin tsqueryout tsqueryrecv tsquerysend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("query representation for text search");
  #define TSQUERYOID		3615
! DATA(insert OID = 3734 ( regconfig		PGNSP PGUID 4 t b N f t \054 0 0 3735 regconfigin regconfigout regconfigrecv regconfigsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered text search configuration");
  #define REGCONFIGOID	3734
! DATA(insert OID = 3769 ( regdictionary	PGNSP PGUID 4 t b N f t \054 0 0 3770 regdictionaryin regdictionaryout regdictionaryrecv regdictionarysend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered text search dictionary");
  #define REGDICTIONARYOID	3769
  
! DATA(insert OID = 3643 ( _tsvector		PGNSP PGUID -1 f b A f t \054 0 3614 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 3644 ( _gtsvector		PGNSP PGUID -1 f b A f t \054 0 3642 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 3645 ( _tsquery		PGNSP PGUID -1 f b A f t \054 0 3615 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 3735 ( _regconfig		PGNSP PGUID -1 f b A f t \054 0 3734 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 3770 ( _regdictionary PGNSP PGUID -1 f b A f t \054 0 3769 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  
! DATA(insert OID = 2970 ( txid_snapshot	PGNSP PGUID -1 f b U f t \054 0 0 2949 txid_snapshot_in txid_snapshot_out txid_snapshot_recv txid_snapshot_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("txid snapshot");
! DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  
  /*
   * pseudo-types
***************
*** 598,628 **** DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 a
   * but there is now support for it in records and arrays.  Perhaps we should
   * just treat it as a regular base type?
   */
! DATA(insert OID = 2249 ( record			PGNSP PGUID -1 f p P f t \054 0 0 2287 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
  #define RECORDOID		2249
! DATA(insert OID = 2287 ( _record		PGNSP PGUID -1 f p P f t \054 0 2249 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
  #define RECORDARRAYOID	2287
! DATA(insert OID = 2275 ( cstring		PGNSP PGUID -2 f p P f t \054 0 0 1263 cstring_in cstring_out cstring_recv cstring_send - - - c p f 0 -1 0 _null_ _null_ ));
  #define CSTRINGOID		2275
! DATA(insert OID = 2276 ( any			PGNSP PGUID  4 t p P f t \054 0 0 0 any_in any_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define ANYOID			2276
! DATA(insert OID = 2277 ( anyarray		PGNSP PGUID -1 f p P f t \054 0 0 0 anyarray_in anyarray_out anyarray_recv anyarray_send - - - d x f 0 -1 0 _null_ _null_ ));
  #define ANYARRAYOID		2277
! DATA(insert OID = 2278 ( void			PGNSP PGUID  4 t p P f t \054 0 0 0 void_in void_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define VOIDOID			2278
! DATA(insert OID = 2279 ( trigger		PGNSP PGUID  4 t p P f t \054 0 0 0 trigger_in trigger_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define TRIGGEROID		2279
! DATA(insert OID = 2280 ( language_handler	PGNSP PGUID  4 t p P f t \054 0 0 0 language_handler_in language_handler_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define LANGUAGE_HANDLEROID		2280
! DATA(insert OID = 2281 ( internal		PGNSP PGUID  SIZEOF_POINTER t p P f t \054 0 0 0 internal_in internal_out - - - - - ALIGNOF_POINTER p f 0 -1 0 _null_ _null_ ));
  #define INTERNALOID		2281
! DATA(insert OID = 2282 ( opaque			PGNSP PGUID  4 t p P f t \054 0 0 0 opaque_in opaque_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define OPAQUEOID		2282
! DATA(insert OID = 2283 ( anyelement		PGNSP PGUID  4 t p P f t \054 0 0 0 anyelement_in anyelement_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define ANYELEMENTOID	2283
! DATA(insert OID = 2776 ( anynonarray	PGNSP PGUID  4 t p P f t \054 0 0 0 anynonarray_in anynonarray_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define ANYNONARRAYOID	2776
! DATA(insert OID = 3500 ( anyenum		PGNSP PGUID  4 t p P f t \054 0 0 0 anyenum_in anyenum_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define ANYENUMOID		3500
  
  
--- 612,642 ----
   * but there is now support for it in records and arrays.  Perhaps we should
   * just treat it as a regular base type?
   */
! DATA(insert OID = 2249 ( record			PGNSP PGUID -1 f p P f t \054 0 0 2287 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  #define RECORDOID		2249
! DATA(insert OID = 2287 ( _record		PGNSP PGUID -1 f p P f t \054 0 2249 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  #define RECORDARRAYOID	2287
! DATA(insert OID = 2275 ( cstring		PGNSP PGUID -2 f p P f t \054 0 0 1263 cstring_in cstring_out cstring_recv cstring_send - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  #define CSTRINGOID		2275
! DATA(insert OID = 2276 ( any			PGNSP PGUID  4 t p P f t \054 0 0 0 any_in any_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define ANYOID			2276
! DATA(insert OID = 2277 ( anyarray		PGNSP PGUID -1 f p P f t \054 0 0 0 anyarray_in anyarray_out anyarray_recv anyarray_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  #define ANYARRAYOID		2277
! DATA(insert OID = 2278 ( void			PGNSP PGUID  4 t p P f t \054 0 0 0 void_in void_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define VOIDOID			2278
! DATA(insert OID = 2279 ( trigger		PGNSP PGUID  4 t p P f t \054 0 0 0 trigger_in trigger_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define TRIGGEROID		2279
! DATA(insert OID = 2280 ( language_handler	PGNSP PGUID  4 t p P f t \054 0 0 0 language_handler_in language_handler_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define LANGUAGE_HANDLEROID		2280
! DATA(insert OID = 2281 ( internal		PGNSP PGUID  SIZEOF_POINTER t p P f t \054 0 0 0 internal_in internal_out - - - - - ALIGNOF_POINTER p f 0 -1 0 -1 f _null_ _null_ ));
  #define INTERNALOID		2281
! DATA(insert OID = 2282 ( opaque			PGNSP PGUID  4 t p P f t \054 0 0 0 opaque_in opaque_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define OPAQUEOID		2282
! DATA(insert OID = 2283 ( anyelement		PGNSP PGUID  4 t p P f t \054 0 0 0 anyelement_in anyelement_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define ANYELEMENTOID	2283
! DATA(insert OID = 2776 ( anynonarray	PGNSP PGUID  4 t p P f t \054 0 0 0 anynonarray_in anynonarray_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define ANYNONARRAYOID	2776
! DATA(insert OID = 3500 ( anyenum		PGNSP PGUID  4 t p P f t \054 0 0 0 anyenum_in anyenum_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define ANYENUMOID		3500
  
  
*** a/src/include/catalog/pg_type_fn.h
--- b/src/include/catalog/pg_type_fn.h
***************
*** 50,56 **** extern Oid TypeCreate(Oid newTypeOid,
  		   char storage,
  		   int32 typeMod,
  		   int32 typNDims,
! 		   bool typeNotNull);
  
  extern void GenerateTypeDependencies(Oid typeNamespace,
  						 Oid typeObjectId,
--- 50,58 ----
  		   char storage,
  		   int32 typeMod,
  		   int32 typNDims,
!     	   bool typeNotNull,
! 		   int32 typeNLabels,
! 		   bool typeSorted);
  
  extern void GenerateTypeDependencies(Oid typeNamespace,
  						 Oid typeObjectId,
*** a/src/include/commands/typecmds.h
--- b/src/include/commands/typecmds.h
***************
*** 24,29 **** extern void RemoveTypes(DropStmt *drop);
--- 24,30 ----
  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);
  
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
***************
*** 348,354 **** typedef enum NodeTag
  	T_DropUserMappingStmt,
  	T_AlterTableSpaceOptionsStmt,
  	T_SecLabelStmt,
! 
  	/*
  	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
  	 */
--- 348,354 ----
  	T_DropUserMappingStmt,
  	T_AlterTableSpaceOptionsStmt,
  	T_SecLabelStmt,
! 	T_AlterEnumStmt,
  	/*
  	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
  	 */
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 2191,2196 **** typedef struct CreateEnumStmt
--- 2191,2210 ----
  
  
  /* ----------------------
+  *		Alter Type Statement, enum types
+  * ----------------------
+  */
+ typedef struct AlterEnumStmt
+ {
+ 	NodeTag		type;
+ 	List	   *typeName;		/* qualified name (list of Value strings) */
+ 	char	   *newVal;			/* new enum value */
+ 	char	   *newValNeighbour;/* neighbouring enum value */
+ 	bool	    newValIsAfter;	/* new enum value is after neighbour? */
+ } AlterEnumStmt;
+ 
+ 
+ /* ----------------------
   *		Create View Statement
   * ----------------------
   */
*** a/src/test/regress/expected/enum.out
--- b/src/test/regress/expected/enum.out
***************
*** 25,30 **** ERROR:  invalid input value for enum rainbow: "mauve"
--- 25,108 ----
  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 typnlabels, typsorted
+ FROM pg_type
+ WHERE oid = 'planets'::regtype;
+  typnlabels | typsorted 
+ ------------+-----------
+           4 | t
+ (1 row)
+ 
+ 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 typnlabels, typsorted
+ FROM pg_type
+ WHERE oid = 'planets'::regtype;
+  typnlabels | typsorted 
+ ------------+-----------
+           8 | f
+ (1 row)
+ 
+ SELECT enumlabel, enumsortorder 
+ FROM pg_enum 
+ WHERE enumtypid = 'planets'::regtype
+ ORDER by 2;
+  enumlabel | enumsortorder 
+ -----------+---------------
+  mercury   |             1
+  venus     |             2
+  earth     |             3
+  mars      |             4
+  jupiter   |             5
+  saturn    |             6
+  uranus    |             7
+  neptune   |             8
+ (8 rows)
+ 
+ select 'mars'::planets > 'mercury' as using_sortorder;
+  using_sortorder 
+ -----------------
+  t
+ (1 row)
+ 
+ -- 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 label.
+ DROP TYPE planets;
+ --
  -- Basic table creation, row selection
  --
  CREATE TABLE enumtest (col rainbow);
***************
*** 403,409 **** 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 
! -----------+-----------
  (0 rows)
  
--- 481,487 ----
  
  SELECT * FROM pg_enum WHERE NOT EXISTS
    (SELECT 1 FROM pg_type WHERE pg_type.oid = enumtypid);
!  enumtypid | enumlabel | enumsortorder 
! -----------+-----------+---------------
  (0 rows)
  
*** a/src/test/regress/sql/enum.sql
--- b/src/test/regress/sql/enum.sql
***************
*** 16,21 **** SELECT 'red'::rainbow;
--- 16,69 ----
  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 typnlabels, typsorted
+ FROM pg_type
+ WHERE oid = 'planets'::regtype;
+ 
+ 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 typnlabels, typsorted
+ FROM pg_type
+ WHERE oid = 'planets'::regtype;
+ 
+ SELECT enumlabel, enumsortorder 
+ FROM pg_enum 
+ WHERE enumtypid = 'planets'::regtype
+ ORDER by 2;
+ 
+ select 'mars'::planets > 'mercury' as using_sortorder;
+ 
+ -- errors for adding labels 
+ ALTER TYPE planets ADD 
+ 	  'plutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutopluto';
+ 
+ ALTER TYPE planets ADD 'pluto' AFTER 'zeus';
+ 
+ DROP TYPE planets;
+ --
  -- Basic table creation, row selection
  --
  CREATE TABLE enumtest (col rainbow);
#32Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Andrew Dunstan (#31)
Re: WIP: extensible enums

On 29 September 2010 20:46, Andrew Dunstan <andrew@dunslane.net> wrote:

Attached is a a slightly updated version of this with the bitrot fixed.

cheers

andrew

Hi,

I had a quick look at this last night. I haven't had time to give it a
full review, but I did spot a couple of things:

1). It still has no docs.

2). In enum_ccmp(), when you cache the full list of elements, you're
not updating mycache->sort_list_length, so it will keep fetching the
full list each time. Also, I think that function could use a few more
comments.

3). I think you need to update psql so that \dT+ returns the enum
elements in the right order.

Otherwise I like it, and I definitely prefer the flexibility that this
syntax gives.

Regards,
Dean

#33Andrew Dunstan
andrew@dunslane.net
In reply to: Dean Rasheed (#32)
Re: WIP: extensible enums

On 10/01/2010 04:35 AM, Dean Rasheed wrote:

2). In enum_ccmp(), when you cache the full list of elements, you're
not updating mycache->sort_list_length, so it will keep fetching the
full list each time. Also, I think that function could use a few more
comments.

Good catch. Will fix.

3). I think you need to update psql so that \dT+ returns the enum
elements in the right order.

Yeah. Will do.

I will post a revised patch soon.

cheers

andrew

#34Robert Haas
robertmhaas@gmail.com
In reply to: Andrew Dunstan (#33)
Re: WIP: extensible enums

On Fri, Oct 1, 2010 at 7:12 AM, Andrew Dunstan <andrew@dunslane.net> wrote:

On 10/01/2010 04:35 AM, Dean Rasheed wrote:

2). In enum_ccmp(), when you cache the full list of elements, you're
not updating mycache->sort_list_length, so it will keep fetching the
full list each time. Also, I think that function could use a few more
comments.

Good catch. Will fix.

3). I think you need to update psql so that \dT+ returns the enum
elements in the right order.

Yeah. Will do.

I will post a revised patch soon.

Should we postpone this to the next CommitFest?

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

#35Andrew Dunstan
andrew@dunslane.net
In reply to: Robert Haas (#34)
1 attachment(s)
Re: WIP: extensible enums

On 10/13/2010 02:08 AM, Robert Haas wrote:

On Fri, Oct 1, 2010 at 7:12 AM, Andrew Dunstan<andrew@dunslane.net> wrote:

On 10/01/2010 04:35 AM, Dean Rasheed wrote:

2). In enum_ccmp(), when you cache the full list of elements, you're
not updating mycache->sort_list_length, so it will keep fetching the
full list each time. Also, I think that function could use a few more
comments.

Good catch. Will fix.

3). I think you need to update psql so that \dT+ returns the enum
elements in the right order.

Yeah. Will do.

I will post a revised patch soon.

Should we postpone this to the next CommitFest?

Sorry, got distracted. Here's a new patch that fixes the above and also
contains some documentation.

cheers

andrew

Attachments:

venum4.patchtext/x-patch; name=venum4.patchDownload
*** a/contrib/pg_upgrade/function.c
--- b/contrib/pg_upgrade/function.c
***************
*** 61,85 **** install_support_functions(migratorContext *ctx)
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 				"		binary_upgrade.set_next_heap_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_toast_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_index_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			 "		binary_upgrade.add_pg_enum_label(OID, OID, NAME) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
--- 61,85 ----
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 					 "		binary_upgrade.set_next_pg_enum_oid(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 				"		binary_upgrade.set_next_heap_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_toast_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_index_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
*** a/contrib/pg_upgrade_support/pg_upgrade_support.c
--- b/contrib/pg_upgrade_support/pg_upgrade_support.c
***************
*** 30,35 **** PG_MODULE_MAGIC;
--- 30,36 ----
  extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
  extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_array_oid;
  extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_toast_oid;
+ extern PGDLLIMPORT Oid binary_upgrade_next_pg_enum_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;
***************
*** 37,54 **** extern PGDLLIMPORT Oid binary_upgrade_next_index_relfilenode;
  Datum		set_next_pg_type_oid(PG_FUNCTION_ARGS);
  Datum		set_next_pg_type_array_oid(PG_FUNCTION_ARGS);
  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);
  
  PG_FUNCTION_INFO_V1(set_next_pg_type_oid);
  PG_FUNCTION_INFO_V1(set_next_pg_type_array_oid);
  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);
  
  Datum
  set_next_pg_type_oid(PG_FUNCTION_ARGS)
--- 38,55 ----
  Datum		set_next_pg_type_oid(PG_FUNCTION_ARGS);
  Datum		set_next_pg_type_array_oid(PG_FUNCTION_ARGS);
  Datum		set_next_pg_type_toast_oid(PG_FUNCTION_ARGS);
+ Datum       set_next_pg_enum_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);
  
  PG_FUNCTION_INFO_V1(set_next_pg_type_oid);
  PG_FUNCTION_INFO_V1(set_next_pg_type_array_oid);
  PG_FUNCTION_INFO_V1(set_next_pg_type_toast_oid);
+ PG_FUNCTION_INFO_V1(set_next_pg_enum_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);
  
  Datum
  set_next_pg_type_oid(PG_FUNCTION_ARGS)
***************
*** 81,86 **** set_next_pg_type_toast_oid(PG_FUNCTION_ARGS)
--- 82,97 ----
  }
  
  Datum
+ set_next_pg_enum_oid(PG_FUNCTION_ARGS)
+ {
+ 	Oid			enumoid = PG_GETARG_OID(0);
+ 
+ 	binary_upgrade_next_pg_enum_oid = enumoid;
+ 
+ 	PG_RETURN_VOID();
+ }
+ 
+ Datum
  set_next_heap_relfilenode(PG_FUNCTION_ARGS)
  {
  	Oid			relfilenode = PG_GETARG_OID(0);
***************
*** 110,124 **** set_next_index_relfilenode(PG_FUNCTION_ARGS)
  	PG_RETURN_VOID();
  }
  
- Datum
- add_pg_enum_label(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);
- 
- 	PG_RETURN_VOID();
- }
--- 121,123 ----
*** a/doc/src/sgml/ref/alter_type.sgml
--- b/doc/src/sgml/ref/alter_type.sgml
***************
*** 23,33 **** 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> 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>
  
  <phrase>where <replaceable class="PARAMETER">action</replaceable> is one of:</phrase>
  
--- 23,34 ----
  
   <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> 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,108 **** ALTER TYPE <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <replace
--- 104,131 ----
       </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 sort order is not set using <literal>BEFORE</literal> or 
+ 	  <literal>AFTER</literal>, then the new item is placed at the end of
+ 	  the list of values.
+      </para>
+ 	 <note>
+ 	  <para>
+ 	   Adding a new enum value will in some cases lower the comparison and 
+ 	   sorting performance of the enum type. This will only usually occur if  
+ 	   <literal>BEFORE</literal> or <literal>AFTER</literal> are used to set 
+ 	   the new value's sort order position somewhere other than at the end 
+ 	   of the list. Optimal performance will be restored if the database is 
+ 	   dumped and reloaded.
+ 	  </para>
+ 	 </note>
+     </listitem>
+    </varlistentry>
    </variablelist>
    </para>
  
***************
*** 196,201 **** ALTER TYPE <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <replace
--- 219,254 ----
        </listitem>
       </varlistentry>
  
+      <varlistentry>
+       <term><replaceable class="PARAMETER">data_type</replaceable></term>
+       <listitem>
+        <para>
+         The data type of the attribute to add, or the new type of the
+         attribute to alter.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
+      <varlistentry>
+       <term><replaceable class="PARAMETER">new_enum_value</replaceable></term>
+       <listitem>
+        <para>
+         The new value to be added to the num type's list of values. Like all
+ 		enum literals it needs to be quoted.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
+      <varlistentry>
+       <term><replaceable class="PARAMETER">exisiting_enum_value</replaceable></term>
+       <listitem>
+        <para>
+         The neighbour of the new value to be added to the num type's list of values. Like all
+ 		enum literals it needs to be quoted.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
      </variablelist>
     </para>
    </refsect1>
***************
*** 232,237 **** ALTER TYPE email SET SCHEMA customers;
--- 285,297 ----
  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>
  
   <refsect1>
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 855,861 **** AddNewRelationType(const char *typeName,
  				   'x',			/* fully TOASTable */
  				   -1,			/* typmod */
  				   0,			/* array dimensions for typBaseType */
! 				   false);		/* Type NOT NULL */
  }
  
  /* --------------------------------
--- 855,863 ----
  				   'x',			/* fully TOASTable */
  				   -1,			/* typmod */
  				   0,			/* array dimensions for typBaseType */
! 				   false,		/* Type NOT NULL */
! 				   -1,          /* no enum labels */
! 				   false);      /* type is not an enum, so not sorted */
  }
  
  /* --------------------------------
***************
*** 1108,1114 **** heap_create_with_catalog(const char *relname,
  				   'x',			/* fully TOASTable */
  				   -1,			/* typmod */
  				   0,			/* array dimensions for typBaseType */
! 				   false);		/* Type NOT NULL */
  
  		pfree(relarrayname);
  	}
--- 1110,1118 ----
  				   'x',			/* fully TOASTable */
  				   -1,			/* typmod */
  				   0,			/* array dimensions for typBaseType */
! 				   false,		/* Type NOT NULL */
! 				   -1,          /* no enum labels */
! 				   false);      /* type is not an enum, so not sorted */
  
  		pfree(relarrayname);
  	}
*** a/src/backend/catalog/pg_enum.c
--- b/src/backend/catalog/pg_enum.c
***************
*** 18,29 ****
--- 18,219 ----
  #include "catalog/catalog.h"
  #include "catalog/indexing.h"
  #include "catalog/pg_enum.h"
+ #include "catalog/pg_type.h"
+ #include "utils/lsyscache.h"
+ #include "utils/syscache.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
  #include "utils/rel.h"
  #include "utils/tqual.h"
  
  static int	oid_cmp(const void *p1, const void *p2);
+ static int	sort_order_cmp(const void *p1, const void *p2);
+ 
+ Oid      binary_upgrade_next_pg_enum_oid = InvalidOid;
+ 
+ /*
+  * 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.
+  *
+  * Returns true iff the labels are sorted by oid after the addition.
+  */
+ 
+ bool
+ AddEnumLabel(Oid enumTypeOid, 
+ 			 char *newVal, 
+ 			 char *neighbour, 
+ 			 bool newValIsAfter,
+ 			 int  nelems,
+ 			 bool elems_are_sorted)
+ {
+ 	Oid        newOid;
+ 	Relation   pg_enum;
+ 	TupleDesc	tupDesc;
+ 	Datum		values[Natts_pg_enum];
+ 	bool		nulls[Natts_pg_enum];
+ 	NameData	enumlabel;
+ 	HeapTuple   enum_tup;
+ 	bool        result = elems_are_sorted;
+ 	int         newelemorder;
+ 
+ 	/* 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)));
+ 
+ 	/* get a new OID for the label */
+ 	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
+ 
+ 	if (OidIsValid(binary_upgrade_next_pg_enum_oid))
+ 	{
+ 		if (neighbour != NULL)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("BEFORE/AFTER incompatible with binary upgrade.")));
+ 
+ 		newOid = binary_upgrade_next_pg_enum_oid;
+ 		binary_upgrade_next_pg_enum_oid = InvalidOid;
+ 	}
+ 	else
+ 	{
+ 		/* non upgrade case */
+ 		newOid = GetNewOid(pg_enum);
+ 	}
+ 
+ 	if (neighbour == NULL)
+ 	{
+ 		/* 
+ 		 * Put the new label at the end of the list.
+ 		 * No change to existing tuples is required.
+ 		 */
+ 		newelemorder = nelems + 1;
+ 		/* are the elements still sorted? */
+ 		if (elems_are_sorted)
+ 		{	
+ 			CatCList   *list;
+ 			int			i;
+ 			bool        still_sorted = true;
+ 
+ 			list = SearchSysCacheList1(ENUMTYPOIDNAME, 
+ 									   ObjectIdGetDatum(enumTypeOid));
+ 			for (i = 0; i < nelems; i++)
+ 			{
+ 				HeapTuple tup = &(list->members[i]->tuple);
+ 				Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
+ 
+ 				if (en->enumsortorder == nelems)
+ 				{
+ 					if (HeapTupleGetOid(tup) > newOid)
+ 						still_sorted = false;
+ 					break;
+ 				}
+ 			}
+ 			ReleaseCatCacheList(list);
+ 			if (! still_sorted)
+ 				result = false;
+ 		}
+ 	}
+ 	else 
+ 	{
+ 		/* BEFORE or AFTER specified */
+ 		CatCList   *list;
+ 		int			i;
+ 		HeapTuple    *existing;
+ 		HeapTuple     nbr = NULL;
+ 		Form_pg_enum nbr_en;
+ 
+ 		/* get a list of the existing elements and sort them by enumsortorder */
+ 		list = SearchSysCacheList1(ENUMTYPOIDNAME, 
+ 								   ObjectIdGetDatum(enumTypeOid));
+ 		existing = palloc(nelems * sizeof(HeapTuple));
+ 
+ 		for (i = 0; i < nelems; i++)
+ 			existing[i] = &(list->members[i]->tuple);
+ 
+ 		qsort(existing, nelems, sizeof(HeapTuple), sort_order_cmp);
+ 		
+ 		/* locate the neighbour element */
+ 		for (i = 0; i < nelems; i++)
+ 		{
+ 			Form_pg_enum exists_en;
+ 			exists_en = (Form_pg_enum) GETSTRUCT(existing[i]);
+ 			if (strcmp(NameStr(exists_en->enumlabel),neighbour) == 0)
+ 				nbr = existing[i];
+ 
+ 		}
+ 
+ 		if (nbr == NULL)
+ 		{
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("\"%s\" is not an existing label.", neighbour)));
+ 		}
+ 
+ 		nbr_en = (Form_pg_enum) GETSTRUCT(nbr);
+ 
+ 		/* 
+ 		 * If BEFORE was specified, the new label goes in the neighbour's
+ 		 * position. Otherwise, it goes in the position after that.
+ 		 */
+ 		newelemorder = nbr_en->enumsortorder;
+ 		if (newValIsAfter)
+ 			newelemorder++;
+ 
+ 		/* 
+ 		 * Add 1 to the sortorder of all the labels after where the
+ 		 * new label goes. Do it from the end back so we don't get
+ 		 * uniqueness violations.
+ 		 */
+ 		for (i = nelems - 1; i>= 0; i--)
+ 		{
+ 			HeapTuple newtup;
+ 			Form_pg_enum exst_en = (Form_pg_enum) GETSTRUCT(existing[i]);
+ 			if (exst_en->enumsortorder < newelemorder)
+ 				break;
+ 			
+ 			newtup = heap_copytuple(existing[i]);
+ 			exst_en = (Form_pg_enum) GETSTRUCT(newtup);
+ 			exst_en->enumsortorder ++;
+ 
+ 			simple_heap_update(pg_enum, &newtup->t_self, newtup);
+ 
+ 			CatalogUpdateIndexes(pg_enum, newtup);
+ 
+ 		}
+ 
+ 		ReleaseCatCacheList(list);
+ 
+ 		/* are the labels sorted by OID? */
+ 		if (result && newelemorder > 1)
+ 			result = newOid > HeapTupleGetOid(existing[newelemorder-2]);
+ 		if (result && newelemorder < nelems + 1)
+ 			result = newOid < HeapTupleGetOid(existing[newelemorder-1]);
+  
+ 	}
+ 
+ 	/* set up the new entry */
+ 	tupDesc = pg_enum->rd_att;
+ 	memset(nulls, false, sizeof(nulls));
+ 	values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
+ 	namestrcpy(&enumlabel, newVal);
+ 	values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
+ 	values[Anum_pg_enum_enumsortorder -1] =	Int32GetDatum(newelemorder);
+ 	enum_tup = heap_form_tuple(tupDesc, 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);
+ 
+ 	return result;
+ 
+ }
  
  
  /*
***************
*** 33,40 **** 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)
  {
  	Relation	pg_enum;
  	TupleDesc	tupDesc;
--- 223,229 ----
   * vals is a list of Value strings.
   */
  void
! EnumValuesCreate(Oid enumTypeOid, List *vals)
  {
  	Relation	pg_enum;
  	TupleDesc	tupDesc;
***************
*** 50,58 **** EnumValuesCreate(Oid enumTypeOid, List *vals,
  	num_elems = list_length(vals);
  
  	/*
! 	 * XXX 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?
  	 */
  
  	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
--- 239,247 ----
  	num_elems = list_length(vals);
  
  	/*
! 	 * 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.
! 	 * It is probably not worth trying harder.
  	 */
  
  	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
***************
*** 62,96 **** EnumValuesCreate(Oid enumTypeOid, List *vals,
  	 * Allocate oids
  	 */
  	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
  	{
  		/*
! 		 * 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.
  		 */
! 		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);
  	}
  
  	/* and make the entries */
  	memset(nulls, false, sizeof(nulls));
--- 251,274 ----
  	 * Allocate oids
  	 */
  	oids = (Oid *) palloc(num_elems * sizeof(Oid));
! 
! 	/*
! 	 * 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.
! 	 */
! 	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);
  
  	/* and make the entries */
  	memset(nulls, false, sizeof(nulls));
***************
*** 114,119 **** EnumValuesCreate(Oid enumTypeOid, List *vals,
--- 292,298 ----
  		values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
  		namestrcpy(&enumlabel, lab);
  		values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
+ 		values[Anum_pg_enum_enumsortorder -1] = Int32GetDatum(elemno+1);
  
  		tup = heap_form_tuple(tupDesc, values, nulls);
  		HeapTupleSetOid(tup, oids[elemno]);
***************
*** 164,170 **** EnumValuesDelete(Oid enumTypeOid)
  }
  
  
! /* qsort comparison function */
  static int
  oid_cmp(const void *p1, const void *p2)
  {
--- 343,349 ----
  }
  
  
! /* qsort comparison for oids */
  static int
  oid_cmp(const void *p1, const void *p2)
  {
***************
*** 177,179 **** oid_cmp(const void *p1, const void *p2)
--- 356,371 ----
  		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);
+ 
+ 	return en1->enumsortorder - en2->enumsortorder;
+ }
+ 
*** a/src/backend/catalog/pg_type.c
--- b/src/backend/catalog/pg_type.c
***************
*** 112,117 **** TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId)
--- 112,119 ----
  	values[i++] = ObjectIdGetDatum(InvalidOid); /* typbasetype */
  	values[i++] = Int32GetDatum(-1);	/* typtypmod */
  	values[i++] = Int32GetDatum(0);		/* typndims */
+ 	values[i++] = Int32GetDatum(-1);	/* typnlabels */
+ 	values[i++] = BoolGetDatum(false);	/* typsorted */
  	nulls[i++] = true;			/* typdefaultbin */
  	nulls[i++] = true;			/* typdefault */
  
***************
*** 204,210 **** TypeCreate(Oid newTypeOid,
  		   char storage,
  		   int32 typeMod,
  		   int32 typNDims,		/* Array dimensions for baseType */
! 		   bool typeNotNull)
  {
  	Relation	pg_type_desc;
  	Oid			typeObjectId;
--- 206,214 ----
  		   char storage,
  		   int32 typeMod,
  		   int32 typNDims,		/* Array dimensions for baseType */
! 		   bool typeNotNull,
! 		   int32 typeNLabels,
! 		   bool typeSorted)
  {
  	Relation	pg_type_desc;
  	Oid			typeObjectId;
***************
*** 342,347 **** TypeCreate(Oid newTypeOid,
--- 346,353 ----
  	values[i++] = ObjectIdGetDatum(baseType);	/* typbasetype */
  	values[i++] = Int32GetDatum(typeMod);		/* typtypmod */
  	values[i++] = Int32GetDatum(typNDims);		/* typndims */
+ 	values[i++] = Int32GetDatum(typeNLabels);	/* typnlabels */
+ 	values[i++] = BoolGetDatum(typeSorted);	    /* typsorted */
  
  	/*
  	 * initialize the default binary value for this type.  Check for nulls of
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
***************
*** 85,96 **** 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 char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
  					Oid baseTypeOid,
  					int typMod, Constraint *constr,
  					char *domainName);
- 
- 
  /*
   * DefineType
   *		Registers a new base type.
--- 85,95 ----
  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 checkEnumOwner(HeapTuple tup, TypeName *typename);
  static char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
  					Oid baseTypeOid,
  					int typMod, Constraint *constr,
  					char *domainName);
  /*
   * DefineType
   *		Registers a new base type.
***************
*** 562,569 **** DefineType(List *names, List *parameters)
  				   storage,		/* TOAST strategy */
  				   -1,			/* typMod (Domains only) */
  				   0,			/* Array Dimensions of typbasetype */
! 				   false);		/* Type NOT NULL */
! 
  	/*
  	 * Create the array type that goes with it.
  	 */
--- 561,569 ----
  				   storage,		/* TOAST strategy */
  				   -1,			/* typMod (Domains only) */
  				   0,			/* Array Dimensions of typbasetype */
! 				   false,		/* Type NOT NULL */
! 				   -1,          /* no enum labels */
! 				   false);        /* type is not an enum, so not sorted */
  	/*
  	 * Create the array type that goes with it.
  	 */
***************
*** 601,607 **** DefineType(List *names, List *parameters)
  			   'x',				/* ARRAY is always toastable */
  			   -1,				/* typMod (Domains only) */
  			   0,				/* Array dimensions of typbasetype */
! 			   false);			/* Type NOT NULL */
  
  	pfree(array_type);
  }
--- 601,609 ----
  			   'x',				/* ARRAY is always toastable */
  			   -1,				/* typMod (Domains only) */
  			   0,				/* Array dimensions of typbasetype */
! 			   false,			/* Type NOT NULL */
! 			   -1,              /* no enum labels */
! 			   false);          /* type is not an enum, so not sorted */
  
  	pfree(array_type);
  }
***************
*** 1044,1050 **** DefineDomain(CreateDomainStmt *stmt)
  				   storage,		/* TOAST strategy */
  				   basetypeMod, /* typeMod value */
  				   typNDims,	/* Array dimensions for base type */
! 				   typNotNull); /* Type NOT NULL */
  
  	/*
  	 * Process constraints which refer to the domain ID returned by TypeCreate
--- 1046,1054 ----
  				   storage,		/* TOAST strategy */
  				   basetypeMod, /* typeMod value */
  				   typNDims,	/* Array dimensions for base type */
! 				   typNotNull,  /* Type NOT NULL */
! 				   -1,          /* no enum labels */
! 				   false);      /* type is not an enum, so not sorted */
  
  	/*
  	 * Process constraints which refer to the domain ID returned by TypeCreate
***************
*** 1094,1099 **** DefineEnum(CreateEnumStmt *stmt)
--- 1098,1106 ----
  	AclResult	aclresult;
  	Oid			old_type_oid;
  	Oid			enumArrayOid;
+ 	int         num_labels;
+ 
+ 	num_labels = list_length(stmt->vals);
  
  	/* Convert list of names to a name and namespace */
  	enumNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
***************
*** 1153,1162 **** DefineEnum(CreateEnumStmt *stmt)
  				   'p',			/* TOAST strategy always plain */
  				   -1,			/* typMod (Domains only) */
  				   0,			/* Array dimensions of typbasetype */
! 				   false);		/* Type NOT NULL */
  
  	/* Enter the enum's values into pg_enum */
! 	EnumValuesCreate(enumTypeOid, stmt->vals, InvalidOid);
  
  	/*
  	 * Create the array type that goes with it.
--- 1160,1171 ----
  				   'p',			/* TOAST strategy always plain */
  				   -1,			/* typMod (Domains only) */
  				   0,			/* Array dimensions of typbasetype */
! 				   false,		/* Type NOT NULL */
! 				   num_labels,  /* count enum labels */
! 				   true);       /* enums always start sorted */
  
  	/* Enter the enum's values into pg_enum */
! 	EnumValuesCreate(enumTypeOid, stmt->vals);
  
  	/*
  	 * Create the array type that goes with it.
***************
*** 1192,1202 **** DefineEnum(CreateEnumStmt *stmt)
  			   'x',				/* ARRAY is always toastable */
  			   -1,				/* typMod (Domains only) */
  			   0,				/* Array dimensions of typbasetype */
! 			   false);			/* Type NOT NULL */
  
  	pfree(enumArrayName);
  }
  
  
  /*
   * Find suitable I/O functions for a type.
--- 1201,1288 ----
  			   'x',				/* ARRAY is always toastable */
  			   -1,				/* typMod (Domains only) */
  			   0,				/* Array dimensions of typbasetype */
! 			   false,			/* Type NOT NULL */
! 			   -1,              /* no enum labels */
! 			   false);          /* type is not an enum, so not sorted */
  
  	pfree(enumArrayName);
  }
  
+ /*
+  * AlterEnum
+  *		Registers a new label for an existing enum.
+  */
+ void
+ AlterEnum (AlterEnumStmt *stmt)
+ {
+ 	Oid			enum_type_oid;
+ 	TypeName  *typename;
+ 	bool       sorted;
+ 	HeapTuple tup, newtup;
+ 	Relation  rel;
+ 	Form_pg_type typTup;
+ 
+ 	/* Make a TypeName so we can use standard type lookup machinery */
+ 	typename = makeTypeNameFromNameList(stmt->typeName);
+ 	enum_type_oid = typenameTypeId(NULL, typename, NULL);
+ 
+ 	/* Look up the row in the type table */
+ 	rel = heap_open(TypeRelationId, RowExclusiveLock);
+ 
+ 	tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(enum_type_oid));
+ 	if (!HeapTupleIsValid(tup))
+ 		elog(ERROR, "cache lookup failed for type %u", enum_type_oid);
+ 
+ 	/* Copy the syscache entry so we can scribble on it below */
+ 	newtup = heap_copytuple(tup);
+ 	ReleaseSysCache(tup);
+ 	tup = newtup;
+ 	typTup = (Form_pg_type) GETSTRUCT(tup);
+ 
+ 	/* Check it's an enum and check user has permission to ALTER the enum */
+ 	checkEnumOwner(tup, typename);
+ 
+ 	/* Add the new label */
+ 	sorted = AddEnumLabel (enum_type_oid, stmt->newVal, 
+ 						   stmt->newValNeighbour, stmt->newValIsAfter,
+ 						   typTup->typnlabels, typTup->typsorted);
+ 
+ 	/* Update the row in pg_type */
+ 	typTup->typnlabels += 1;
+ 	typTup->typsorted = sorted;
+ 
+ 	simple_heap_update(rel, &tup->t_self, tup);
+ 
+ 	CatalogUpdateIndexes(rel, tup);
+ 
+ 	/* Clean up */
+ 	heap_close(rel, RowExclusiveLock);
+ }
+ 
+ 
+ /*
+  * 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, TypeName *typename)
+ {
+ 	Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup);
+ 
+ 	/* Check that this is actually a domain */
+ 	if (typTup->typtype != TYPTYPE_ENUM)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 				 errmsg("\"%s\" is not an enum",
+ 						TypeNameToString(typename))));
+ 
+ 	/* 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.
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2873,2878 **** _copyCreateEnumStmt(CreateEnumStmt *from)
--- 2873,2891 ----
  	return newnode;
  }
  
+ static AlterEnumStmt *
+ _copyAlterEnumStmt(AlterEnumStmt *from)
+ {
+ 	AlterEnumStmt *newnode = makeNode(AlterEnumStmt);
+ 
+ 	COPY_NODE_FIELD(typeName);
+ 	COPY_STRING_FIELD(newVal);
+ 	COPY_STRING_FIELD(newValNeighbour);
+ 	COPY_SCALAR_FIELD(newValIsAfter);
+ 
+ 	return newnode;
+ }
+ 
  static ViewStmt *
  _copyViewStmt(ViewStmt *from)
  {
***************
*** 4033,4038 **** copyObject(void *from)
--- 4046,4054 ----
  		case T_CreateEnumStmt:
  			retval = _copyCreateEnumStmt(from);
  			break;
+ 		case T_AlterEnumStmt:
+ 			retval = _copyAlterEnumStmt(from);
+ 			break;
  		case T_ViewStmt:
  			retval = _copyViewStmt(from);
  			break;
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 1390,1395 **** _equalCreateEnumStmt(CreateEnumStmt *a, CreateEnumStmt *b)
--- 1390,1406 ----
  }
  
  static bool
+ _equalAlterEnumStmt(AlterEnumStmt *a, AlterEnumStmt *b)
+ {
+ 	COMPARE_NODE_FIELD(typeName);
+ 	COMPARE_STRING_FIELD(newVal);
+ 	COMPARE_STRING_FIELD(newValNeighbour);
+ 	COMPARE_SCALAR_FIELD(newValIsAfter);
+ 
+ 	return true;
+ }
+ 
+ static bool
  _equalViewStmt(ViewStmt *a, ViewStmt *b)
  {
  	COMPARE_NODE_FIELD(view);
***************
*** 2697,2702 **** equal(void *a, void *b)
--- 2708,2716 ----
  		case T_CreateEnumStmt:
  			retval = _equalCreateEnumStmt(a, b);
  			break;
+ 		case T_AlterEnumStmt:
+ 			retval = _equalAlterEnumStmt(a, b);
+ 			break;
  		case T_ViewStmt:
  			retval = _equalViewStmt(a, b);
  			break;
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 182,189 **** static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
  }
  
  %type <node>	stmt schema_stmt
! 		AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterFdwStmt
! 		AlterForeignServerStmt AlterGroupStmt
  		AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
  		AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
  		AlterRoleStmt AlterRoleSetStmt
--- 182,189 ----
  }
  
  %type <node>	stmt schema_stmt
!         AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
!         AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
  		AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
  		AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
  		AlterRoleStmt AlterRoleSetStmt
***************
*** 652,657 **** stmt :
--- 652,658 ----
  			| AlterDatabaseSetStmt
  			| AlterDefaultPrivilegesStmt
  			| AlterDomainStmt
+ 			| AlterEnumStmt
  			| AlterFdwStmt
  			| AlterForeignServerStmt
  			| AlterFunctionStmt
***************
*** 3862,3867 **** enum_val_list:	Sconst
--- 3863,3908 ----
  				{ $$ = 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->newValNeighbour = 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->newValNeighbour = $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->newValNeighbour = $7;
+ 				 n->newValIsAfter = true;
+ 
+ 				 $$ = (Node *) n;
+ 			 }
+ 		 ;
+ 
+ 
  
  /*****************************************************************************
   *
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 190,195 **** check_xact_readonly(Node *parsetree)
--- 190,196 ----
  		case T_CreateTrigStmt:
  		case T_CompositeTypeStmt:
  		case T_CreateEnumStmt:
+ 		case T_AlterEnumStmt:
  		case T_ViewStmt:
  		case T_DropCastStmt:
  		case T_DropStmt:
***************
*** 860,865 **** standard_ProcessUtility(Node *parsetree,
--- 861,870 ----
  			DefineEnum((CreateEnumStmt *) parsetree);
  			break;
  
+ 		case T_AlterEnumStmt:	/* ALTER TYPE (enum) */
+ 			AlterEnum((AlterEnumStmt *) parsetree);
+ 			break;
+ 
  		case T_ViewStmt:		/* CREATE VIEW */
  			DefineView((ViewStmt *) parsetree, queryString);
  			break;
***************
*** 1868,1873 **** CreateCommandTag(Node *parsetree)
--- 1873,1882 ----
  			tag = "CREATE TYPE";
  			break;
  
+ 		case T_AlterEnumStmt:
+ 			tag = "ALTER TYPE";
+ 			break;
+ 
  		case T_ViewStmt:
  			tag = "CREATE VIEW";
  			break;
***************
*** 2410,2415 **** GetCommandLogLevel(Node *parsetree)
--- 2419,2428 ----
  			lev = LOGSTMT_DDL;
  			break;
  
+ 		case T_AlterEnumStmt:
+ 			lev = LOGSTMT_DDL;
+ 			break;
+ 
  		case T_ViewStmt:
  			lev = LOGSTMT_DDL;
  			break;
*** a/src/backend/utils/adt/enum.c
--- b/src/backend/utils/adt/enum.c
***************
*** 14,19 ****
--- 14,20 ----
  #include "postgres.h"
  
  #include "catalog/pg_enum.h"
+ #include "catalog/pg_type.h"
  #include "fmgr.h"
  #include "utils/array.h"
  #include "utils/builtins.h"
***************
*** 22,30 ****
  #include "libpq/pqformat.h"
  #include "miscadmin.h"
  
  
  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 */
--- 23,48 ----
  #include "libpq/pqformat.h"
  #include "miscadmin.h"
  
+ typedef struct 
+ {
+ 	Oid      enum_oid;
+ 	int32   sort_order;
+ } enum_sort;
+ 
+ typedef struct 
+ {
+ 	Oid       enumtypoid;
+ 	bool      oids_are_sorted;
+ 	int       sort_list_length;
+ 	int       label_count;
+ 	enum_sort sort_order_list[1];
+ } enum_sort_cache;
+ 	
+ 
  
  static ArrayType *enum_range_internal(Oid enumtypoid, Oid lower, Oid upper);
! static int	enum_sort_cmp(const void *left, const void *right);
! static int	enum_oid_cmp(const void *left, const void *right);
  
  
  /* Basic I/O support */
***************
*** 155,167 **** enum_send(PG_FUNCTION_ARGS)
  
  /* Comparison functions and related */
  
  Datum
  enum_lt(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a < b);
  }
  
  Datum
--- 173,347 ----
  
  /* Comparison functions and related */
  
+ 
+ /* enum_ccmp is the common engine for all the visible comparison functions */
+ 
+ static inline int 
+ enum_ccmp(Oid arg1, Oid arg2, FunctionCallInfo fcinfo)
+ {
+ 
+ 	/*
+ 	 * Keep a cache of type information in fcinfo->flinfo->fn_extra.
+ 	 * If the Oids are sorted, that's all the info we need, and
+ 	 * we can just return the difference of arg1 and arg2.
+ 	 * If not, we keep a list of Oid/sortorder pairs in the cache,
+ 	 * and look up the sortorder by Oid, using a binary search,
+ 	 * and return the difference between the sortorders. 
+ 	 *
+ 	 * To keep down the cost of retail comparisons even if
+ 	 * the label set is very large, we start by just putting
+ 	 * Oids we are actually comparing into the cache. If this
+ 	 * grows beyond a handful (10 in fact) it looks like it's a
+ 	 * bulk operation, and so we just fetch the all the labels
+ 	 * and sort them. This keeps down the number of times we
+ 	 * might need to call quicksort.
+ 	 */
+ 
+ 	enum_sort_cache * mycache;
+ 	enum_sort *es1, *es2, srch;
+ 	int sort1, sort2;
+ 	bool added = false;
+ 	HeapTuple	enum_tup, type_tup;
+ 	Form_pg_enum en;
+ 	Oid typeoid;
+ 	Form_pg_type typ;
+ 
+ 	/* 
+ 	 * Fast path return for equal Oids, sorted or not. 
+ 	 * This shouldn't happen, but it's here just in case.
+ 	 */
+ 	if (arg1 == arg2)
+ 		return 0;
+ 
+ 	mycache = (enum_sort_cache *) fcinfo->flinfo->fn_extra;
+ 
+ 	/* Initialize the cache if it's not already there */
+ 	if (mycache == NULL )
+ 	{
+ 		enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg1));
+ 		en = (Form_pg_enum) GETSTRUCT(enum_tup);
+ 		typeoid = en->enumtypid;
+ 		type_tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typeoid));
+ 		typ = (Form_pg_type)  GETSTRUCT(type_tup);
+ 		if (typ->typtype != 'e')
+ 			elog(ERROR,"wrong type for oid %u",typeoid);
+         fcinfo->flinfo->fn_extra = 
+ 		  MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ 							 sizeof(enum_sort_cache) + 
+ 							 (typ->typnlabels * sizeof(enum_sort)));
+         mycache = (enum_sort_cache *) fcinfo->flinfo->fn_extra;
+ 		mycache->enumtypoid = typeoid;
+ 		mycache->sort_list_length = 1;
+ 		mycache->label_count = typ->typnlabels;
+ 		mycache->oids_are_sorted = typ->typsorted;
+ 		mycache->sort_order_list[0].enum_oid = arg1;
+ 		mycache->sort_order_list[0].sort_order = en->enumsortorder;
+ 		ReleaseSysCache(type_tup);
+ 		ReleaseSysCache(enum_tup);
+ 	}
+ 
+ 	/* Fast path return for Oids are sorted case */
+ 	if (mycache->oids_are_sorted)
+ 		return arg1 - arg2;
+ 
+ 	/* Look up the Oids in the cache */
+ 	srch.enum_oid = arg1;
+ 	es1 = bsearch(&srch,
+ 				  mycache->sort_order_list,
+ 				  mycache->sort_list_length,
+ 				  sizeof(enum_sort),
+ 				  enum_oid_cmp);
+ 	srch.enum_oid = arg2;
+ 	es2 = bsearch(&srch,
+ 				  mycache->sort_order_list,
+ 				  mycache->sort_list_length,
+ 				  sizeof(enum_sort),
+ 				  enum_oid_cmp);
+ 
+ 	/* Handle cache misses, or get the sort order from the search results */
+ 
+ 	if (es1 == NULL)
+ 	{
+ 		
+ 		enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg1));
+ 		en = (Form_pg_enum) GETSTRUCT(enum_tup);
+ 		mycache->sort_order_list[mycache->sort_list_length].enum_oid = arg1;
+ 		sort1 = en->enumsortorder;
+ 		mycache->sort_order_list[mycache->sort_list_length].sort_order = 
+ 			sort1; 
+ 		ReleaseSysCache(enum_tup);
+ 		mycache->sort_list_length ++;
+ 		added = true;
+ 	}
+ 	else
+ 	{
+ 		/* already in cache */
+ 		sort1 = es1->sort_order;
+ 	}
+ 
+ 	if (es2 == NULL)
+ 	{
+ 		
+ 		enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg2));
+ 		en = (Form_pg_enum) GETSTRUCT(enum_tup);
+ 		sort2 = en->enumsortorder;
+ 		mycache->sort_order_list[mycache->sort_list_length].enum_oid = arg2;
+ 		mycache->sort_order_list[mycache->sort_list_length].sort_order = 
+ 			sort2;
+ 		ReleaseSysCache(enum_tup);
+ 		mycache->sort_list_length ++;
+ 		added = true;
+ 	}
+ 	else
+ 	{
+ 		/* already in cache */
+ 		sort2 = es2->sort_order;
+ 	}
+ 
+ 	/* Sort the cache, and possibly complete it, for the next function call */
+ 	if (added)
+ 	{
+ 		/* 
+ 		 * If we have more than a handful, just fetch them all, so we limit
+ 		 * the number of sort operations required.
+ 		 */
+ 		if (mycache->sort_list_length > 10 && 
+ 			mycache->sort_list_length < mycache->label_count)
+ 		{
+ 			CatCList   *nlist;
+ 			int			num, i;
+ 
+ 			nlist = SearchSysCacheList1(ENUMTYPOIDNAME, 
+ 									   ObjectIdGetDatum(mycache->enumtypoid));
+ 			num = nlist->n_members;
+ 			for (i = 0; i < num; i++)
+ 			{
+ 				HeapTuple tup = &(nlist->members[i]->tuple);
+ 				Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
+ 				
+ 				mycache->sort_order_list[i].enum_oid = HeapTupleGetOid(tup);
+ 				mycache->sort_order_list[i].sort_order = en->enumsortorder; 
+ 			}
+ 			mycache->sort_list_length = mycache->label_count;
+ 
+ 			ReleaseCatCacheList(nlist);
+ 		}
+ 
+ 		qsort(mycache->sort_order_list,mycache->sort_list_length,
+ 			  sizeof(enum_sort),enum_oid_cmp);
+ 	}
+ 
+ 	/* and we're done */
+ 	return sort1 - sort2;
+ }
+ 
  Datum
  enum_lt(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) < 0);
  }
  
  Datum
***************
*** 170,176 **** enum_le(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a <= b);
  }
  
  Datum
--- 350,356 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) <= 0);
  }
  
  Datum
***************
*** 197,203 **** enum_ge(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a >= b);
  }
  
  Datum
--- 377,383 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) >= 0);
  }
  
  Datum
***************
*** 206,212 **** enum_gt(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a > b);
  }
  
  Datum
--- 386,392 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) > 0);
  }
  
  Datum
***************
*** 215,221 **** enum_smaller(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(a <= b ? a : b);
  }
  
  Datum
--- 395,401 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(enum_ccmp(a,b,fcinfo) < 0 ? a : b);
  }
  
  Datum
***************
*** 224,230 **** enum_larger(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(a >= b ? a : b);
  }
  
  Datum
--- 404,410 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(enum_ccmp(a,b,fcinfo) > 0 ? a : b);
  }
  
  Datum
***************
*** 233,242 **** 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)
  		PG_RETURN_INT32(0);
  	else
  		PG_RETURN_INT32(-1);
  }
--- 413,422 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	if (a == b)
  		PG_RETURN_INT32(0);
+ 	else if (enum_ccmp(a,b,fcinfo) > 0)
+ 		PG_RETURN_INT32(1);
  	else
  		PG_RETURN_INT32(-1);
  }
***************
*** 248,253 **** enum_first(PG_FUNCTION_ARGS)
--- 428,434 ----
  {
  	Oid			enumtypoid;
  	Oid			min = InvalidOid;
+ 	int         min_sort = -1; /* value will never in fact be used */
  	CatCList   *list;
  	int			num,
  				i;
***************
*** 267,276 **** enum_first(PG_FUNCTION_ARGS)
  	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);
--- 448,461 ----
  	num = list->n_members;
  	for (i = 0; i < num; i++)
  	{
! 		HeapTuple tup = &(list->members[i]->tuple);
! 		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
! 
! 		if (!OidIsValid(min) || en->enumsortorder < min_sort)
! 		{
! 			min = HeapTupleGetOid(tup);
! 			min_sort = en->enumsortorder;
! 		}
  	}
  
  	ReleaseCatCacheList(list);
***************
*** 287,292 **** enum_last(PG_FUNCTION_ARGS)
--- 472,478 ----
  {
  	Oid			enumtypoid;
  	Oid			max = InvalidOid;
+ 	int         max_sort = -1; /* value will never in fact be used */
  	CatCList   *list;
  	int			num,
  				i;
***************
*** 306,315 **** enum_last(PG_FUNCTION_ARGS)
  	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);
--- 492,505 ----
  	num = list->n_members;
  	for (i = 0; i < num; i++)
  	{
! 		HeapTuple tup = &(list->members[i]->tuple);
! 		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
! 
! 		if (!OidIsValid(max) || en->enumsortorder > max_sort)
! 		{
! 			max = HeapTupleGetOid(tup);
! 			max_sort = en->enumsortorder;
! 		}
  	}
  
  	ReleaseCatCacheList(list);
***************
*** 382,427 **** enum_range_internal(Oid enumtypoid, Oid lower, Oid upper)
  				i,
  				j;
  	Datum	   *elems;
  
  	list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid));
  	total = list->n_members;
  
  	elems = (Datum *) palloc(total * sizeof(Datum));
  
- 	j = 0;
  	for (i = 0; i < total; i++)
  	{
! 		Oid			val = HeapTupleGetOid(&(list->members[i]->tuple));
  
- 		if ((!OidIsValid(lower) || lower <= val) &&
- 			(!OidIsValid(upper) || val <= upper))
- 			elems[j++] = ObjectIdGetDatum(val);
  	}
  
  	/* shouldn't need the cache anymore */
  	ReleaseCatCacheList(list);
  
! 	/* sort results into OID order */
! 	qsort(elems, j, sizeof(Datum), enum_elem_cmp);
  
  	/* note this hardwires some details about the representation of Oid */
  	result = construct_array(elems, j, 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;
  }
--- 572,647 ----
  				i,
  				j;
  	Datum	   *elems;
+ 	enum_sort  *sort_items;
+ 	bool        left_found;
  
  	list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid));
  	total = list->n_members;
  
  	elems = (Datum *) palloc(total * sizeof(Datum));
+ 	sort_items = (enum_sort *) palloc(total * sizeof(enum_sort));
  
  	for (i = 0; i < total; i++)
  	{
! 		HeapTuple tup = &(list->members[i]->tuple);
! 		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
! 
! 		sort_items[i].enum_oid = HeapTupleGetOid(tup);
! 		sort_items[i].sort_order = en->enumsortorder;
  
  	}
  
  	/* shouldn't need the cache anymore */
  	ReleaseCatCacheList(list);
  
! 	/* sort results into sort_order sequence */
! 	qsort(sort_items, total, sizeof(enum_sort), enum_sort_cmp);
! 
! 	j = 0; 
! 	left_found = !OidIsValid(lower);
! 	for (i=0; i < total; i++)
! 	{
! 		if (! left_found && lower == sort_items[i].enum_oid)
! 			left_found = true;
! 
! 		if (left_found)
! 			elems[j++] = ObjectIdGetDatum(sort_items[i].enum_oid);
! 
! 		if (OidIsValid(upper) && upper == sort_items[i].enum_oid)
! 			break;
! 	}
  
  	/* note this hardwires some details about the representation of Oid */
  	result = construct_array(elems, j, enumtypoid, sizeof(Oid), true, 'i');
  
  	pfree(elems);
+ 	pfree(sort_items);
  
  	return result;
  }
  
! /* 
!  * qsort comparison using sort order, for range routines 
!  */
  static int
! enum_sort_cmp(const void *left, const void *right)
  {
! 	enum_sort  *l = (enum_sort *) left;
! 	enum_sort  *r = (enum_sort *) right;
! 
! 	return l->sort_order - r->sort_order;
  }
+ 
+ /* 
+  * qsort comparison using OID order for comparison search cache
+  */
+ static int 
+ enum_oid_cmp(const void *es1, const void *es2)
+ {
+ 	enum_sort *p1, *p2;
+ 	p1 = (enum_sort *)es1;
+ 	p2 = (enum_sort *)es2;
+ 	return p1->enum_oid - p2->enum_oid;
+ }
+ 
+ 
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 6653,6669 **** dumpEnumType(Archive *fout, TypeInfo *tyinfo)
  	PQExpBuffer query = createPQExpBuffer();
  	PGresult   *res;
  	int			num,
! 				i;
  	Oid			enum_oid;
  	char	   *label;
  
  	/* Set proper schema search path so regproc references list correctly */
  	selectSourceSchema(tyinfo->dobj.namespace->dobj.name);
  
! 	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);
--- 6653,6676 ----
  	PQExpBuffer query = createPQExpBuffer();
  	PGresult   *res;
  	int			num,
! 		        i;
  	Oid			enum_oid;
  	char	   *label;
  
  	/* Set proper schema search path so regproc references list correctly */
  	selectSourceSchema(tyinfo->dobj.namespace->dobj.name);
  
!     if  (fout->remoteVersion > 90000)
! 		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);
***************
*** 6709,6725 **** dumpEnumType(Archive *fout, TypeInfo *tyinfo)
  		{
  			enum_oid = atooid(PQgetvalue(res, i, PQfnumber(res, "oid")));
  			label = PQgetvalue(res, i, PQfnumber(res, "enumlabel"));
! 
  			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);
! 			appendStringLiteralAH(q, label, fout);
! 			appendPQExpBuffer(q, ");\n");
  		}
- 		appendPQExpBuffer(q, "\n");
  	}
  
  	ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId,
--- 6716,6734 ----
  		{
  			enum_oid = atooid(PQgetvalue(res, i, PQfnumber(res, "oid")));
  			label = PQgetvalue(res, i, PQfnumber(res, "enumlabel"));
! 			
  			if (i == 0)
  				appendPQExpBuffer(q, "\n-- For binary upgrade, must preserve pg_enum oids\n");
  			appendPQExpBuffer(q,
! 							  "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\n");
  		}
  	}
  
  	ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId,
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
***************
*** 473,489 **** 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"
  						  "      ),\n"
  						  "      E'\\n'\n"
  						  "  ) AS \"%s\",\n",
  						  gettext_noop("Elements"));
  
  	appendPQExpBuffer(&buf,
  				"  pg_catalog.obj_description(t.oid, 'pg_type') as \"%s\"\n",
--- 473,499 ----
  						  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");
! 
! 		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",
*** a/src/include/catalog/indexing.h
--- b/src/include/catalog/indexing.h
***************
*** 147,152 **** DECLARE_UNIQUE_INDEX(pg_enum_oid_index, 3502, on pg_enum using btree(oid oid_ops
--- 147,154 ----
  #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, 3539, on pg_enum using btree(enumtypid oid_ops, enumsortorder int4_ops));
+ #define EnumTypIdSortOrderIndexId 3539
  
  /* 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));
*** a/src/include/catalog/pg_class.h
--- b/src/include/catalog/pg_class.h
***************
*** 132,138 **** typedef FormData_pg_class *Form_pg_class;
   */
  
  /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
! DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f f r 28 0 t f f f f f 3 _null_ _null_ ));
  DESCR("");
  DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f f r 19 0 f f f f f f 3 _null_ _null_ ));
  DESCR("");
--- 132,138 ----
   */
  
  /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
! DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f f r 30 0 t f f f f f 3 _null_ _null_ ));
  DESCR("");
  DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f f r 19 0 f f f f f f 3 _null_ _null_ ));
  DESCR("");
*** a/src/include/catalog/pg_enum.h
--- b/src/include/catalog/pg_enum.h
***************
*** 35,40 **** CATALOG(pg_enum,3501)
--- 35,41 ----
  {
  	Oid			enumtypid;		/* OID of owning enum type */
  	NameData	enumlabel;		/* text representation of enum value */
+ 	int4        enumsortorder;  /* sort order for this enum label */
  } FormData_pg_enum;
  
  /* ----------------
***************
*** 48,56 **** typedef FormData_pg_enum *Form_pg_enum;
   *		compiler constants for pg_enum
   * ----------------
   */
! #define Natts_pg_enum					2
  #define Anum_pg_enum_enumtypid			1
  #define Anum_pg_enum_enumlabel			2
  
  /* ----------------
   *		pg_enum has no initial contents
--- 49,58 ----
   *		compiler constants for pg_enum
   * ----------------
   */
! #define Natts_pg_enum					3
  #define Anum_pg_enum_enumtypid			1
  #define Anum_pg_enum_enumlabel			2
+ #define Anum_pg_enum_enumsortorder		3
  
  /* ----------------
   *		pg_enum has no initial contents
***************
*** 60,67 **** 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 EnumValuesDelete(Oid enumTypeOid);
  
  #endif   /* PG_ENUM_H */
--- 62,70 ----
  /*
   * prototypes for functions in pg_enum.c
   */
! extern void EnumValuesCreate(Oid enumTypeOid, List *vals);
  extern void EnumValuesDelete(Oid enumTypeOid);
+ extern bool AddEnumLabel(Oid enumTypeOid, char *newVal, char *neighbour, 
+ 						 bool newValIsAfter, int nelems, bool elems_are_sorted);
  
  #endif   /* PG_ENUM_H */
*** a/src/include/catalog/pg_type.h
--- b/src/include/catalog/pg_type.h
***************
*** 194,199 **** CATALOG(pg_type,1247) BKI_BOOTSTRAP BKI_ROWTYPE_OID(71) BKI_SCHEMA_MACRO
--- 194,211 ----
  	 */
  	int4		typndims;
  
+     /* 
+      * typnlabels, contains a count on the number of labels an enum type has, 
+      * -1 for a non-enum type.
+      */
+     int4          typnlabels;
+ 
+     /*
+      * typsorted is true if the oids of an enum type reflect the type's sort
+      * order, false otherwise including for a non-enum type.
+      */
+     bool         typsorted;
+ 
  	/*
  	 * If typdefaultbin is not NULL, it is the nodeToString representation of
  	 * a default expression for the type.  Currently this is only used for
***************
*** 224,230 **** typedef FormData_pg_type *Form_pg_type;
   *		compiler constants for pg_type
   * ----------------
   */
! #define Natts_pg_type					28
  #define Anum_pg_type_typname			1
  #define Anum_pg_type_typnamespace		2
  #define Anum_pg_type_typowner			3
--- 236,242 ----
   *		compiler constants for pg_type
   * ----------------
   */
! #define Natts_pg_type					30
  #define Anum_pg_type_typname			1
  #define Anum_pg_type_typnamespace		2
  #define Anum_pg_type_typowner			3
***************
*** 251,258 **** typedef FormData_pg_type *Form_pg_type;
  #define Anum_pg_type_typbasetype		24
  #define Anum_pg_type_typtypmod			25
  #define Anum_pg_type_typndims			26
! #define Anum_pg_type_typdefaultbin		27
! #define Anum_pg_type_typdefault			28
  
  
  /* ----------------
--- 263,272 ----
  #define Anum_pg_type_typbasetype		24
  #define Anum_pg_type_typtypmod			25
  #define Anum_pg_type_typndims			26
! #define Anum_pg_type_typnlabels			27
! #define Anum_pg_type_typsorted			28
! #define Anum_pg_type_typdefaultbin		29
! #define Anum_pg_type_typdefault			30
  
  
  /* ----------------
***************
*** 269,355 **** typedef FormData_pg_type *Form_pg_type;
   */
  
  /* OIDS 1 - 99 */
! DATA(insert OID = 16 (	bool	   PGNSP PGUID	1 t b B t t \054 0	 0 1000 boolin boolout boolrecv boolsend - - - c p f 0 -1 0 _null_ _null_ ));
  DESCR("boolean, 'true'/'false'");
  #define BOOLOID			16
  
! DATA(insert OID = 17 (	bytea	   PGNSP PGUID -1 f b U f t \054 0	0 1001 byteain byteaout bytearecv byteasend - - - i x f 0 -1 0 _null_ _null_ ));
  DESCR("variable-length string, binary values escaped");
  #define BYTEAOID		17
  
! DATA(insert OID = 18 (	char	   PGNSP PGUID	1 t b S f t \054 0	 0 1002 charin charout charrecv charsend - - - c p f 0 -1 0 _null_ _null_ ));
  DESCR("single character");
  #define CHAROID			18
  
! DATA(insert OID = 19 (	name	   PGNSP PGUID NAMEDATALEN f b S f t \054 0 18 1003 namein nameout namerecv namesend - - - c p f 0 -1 0 _null_ _null_ ));
  DESCR("63-character type for storing system identifiers");
  #define NAMEOID			19
  
! DATA(insert OID = 20 (	int8	   PGNSP PGUID	8 FLOAT8PASSBYVAL b N f t \054 0	 0 1016 int8in int8out int8recv int8send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("~18 digit integer, 8-byte storage");
  #define INT8OID			20
  
! DATA(insert OID = 21 (	int2	   PGNSP PGUID	2 t b N f t \054 0	 0 1005 int2in int2out int2recv int2send - - - s p f 0 -1 0 _null_ _null_ ));
  DESCR("-32 thousand to 32 thousand, 2-byte storage");
  #define INT2OID			21
  
! DATA(insert OID = 22 (	int2vector PGNSP PGUID -1 f b A f t \054 0	21 1006 int2vectorin int2vectorout int2vectorrecv int2vectorsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("array of int2, used in system tables");
  #define INT2VECTOROID	22
  
! DATA(insert OID = 23 (	int4	   PGNSP PGUID	4 t b N f t \054 0	 0 1007 int4in int4out int4recv int4send - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("-2 billion to 2 billion integer, 4-byte storage");
  #define INT4OID			23
  
! DATA(insert OID = 24 (	regproc    PGNSP PGUID	4 t b N f t \054 0	 0 1008 regprocin regprocout regprocrecv regprocsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered procedure");
  #define REGPROCOID		24
  
! DATA(insert OID = 25 (	text	   PGNSP PGUID -1 f b S t t \054 0	0 1009 textin textout textrecv textsend - - - i x f 0 -1 0 _null_ _null_ ));
  DESCR("variable-length string, no limit specified");
  #define TEXTOID			25
  
! DATA(insert OID = 26 (	oid		   PGNSP PGUID	4 t b N t t \054 0	 0 1028 oidin oidout oidrecv oidsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("object identifier(oid), maximum 4 billion");
  #define OIDOID			26
  
! DATA(insert OID = 27 (	tid		   PGNSP PGUID	6 f b U f t \054 0	 0 1010 tidin tidout tidrecv tidsend - - - s p f 0 -1 0 _null_ _null_ ));
  DESCR("(block, offset), physical location of tuple");
  #define TIDOID		27
  
! DATA(insert OID = 28 (	xid		   PGNSP PGUID	4 t b U f t \054 0	 0 1011 xidin xidout xidrecv xidsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("transaction id");
  #define XIDOID 28
  
! DATA(insert OID = 29 (	cid		   PGNSP PGUID	4 t b U f t \054 0	 0 1012 cidin cidout cidrecv cidsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("command identifier type, sequence in transaction id");
  #define CIDOID 29
  
! DATA(insert OID = 30 (	oidvector  PGNSP PGUID -1 f b A f t \054 0	26 1013 oidvectorin oidvectorout oidvectorrecv oidvectorsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("array of oids, used in system tables");
  #define OIDVECTOROID	30
  
  /* hand-built rowtype entries for bootstrapped catalogs */
  /* NB: OIDs assigned here must match the BKI_ROWTYPE_OID declarations */
  
! DATA(insert OID = 71 (	pg_type			PGNSP PGUID -1 f c C f t \054 1247 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 75 (	pg_attribute	PGNSP PGUID -1 f c C f t \054 1249 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 81 (	pg_proc			PGNSP PGUID -1 f c C f t \054 1255 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 83 (	pg_class		PGNSP PGUID -1 f c C f t \054 1259 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 100 - 199 */
! DATA(insert OID = 142 ( xml		   PGNSP PGUID -1 f b U f t \054 0 0 143 xml_in xml_out xml_recv xml_send - - - i x f 0 -1 0 _null_ _null_ ));
  DESCR("XML content");
  #define XMLOID 142
! DATA(insert OID = 143 ( _xml	   PGNSP PGUID -1 f b A f t \054 0 142 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  
! DATA(insert OID = 194 (	pg_node_tree	PGNSP PGUID -1 f b S f t \054 0	0 0 pg_node_tree_in pg_node_tree_out pg_node_tree_recv pg_node_tree_send - - - i x f 0 -1 0 _null_ _null_ ));
  DESCR("string representing an internal node tree");
  #define PGNODETREEOID	194
  
  /* OIDS 200 - 299 */
  
! DATA(insert OID = 210 (  smgr	   PGNSP PGUID 2 t b U f t \054 0 0 0 smgrin smgrout - - - - - s p f 0 -1 0 _null_ _null_ ));
  DESCR("storage manager");
  
  /* OIDS 300 - 399 */
--- 283,369 ----
   */
  
  /* OIDS 1 - 99 */
! DATA(insert OID = 16 (	bool	   PGNSP PGUID	1 t b B t t \054 0	 0 1000 boolin boolout boolrecv boolsend - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("boolean, 'true'/'false'");
  #define BOOLOID			16
  
! DATA(insert OID = 17 (	bytea	   PGNSP PGUID -1 f b U f t \054 0	0 1001 byteain byteaout bytearecv byteasend - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("variable-length string, binary values escaped");
  #define BYTEAOID		17
  
! DATA(insert OID = 18 (	char	   PGNSP PGUID	1 t b S f t \054 0	 0 1002 charin charout charrecv charsend - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("single character");
  #define CHAROID			18
  
! DATA(insert OID = 19 (	name	   PGNSP PGUID NAMEDATALEN f b S f t \054 0 18 1003 namein nameout namerecv namesend - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("63-character type for storing system identifiers");
  #define NAMEOID			19
  
! DATA(insert OID = 20 (	int8	   PGNSP PGUID	8 FLOAT8PASSBYVAL b N f t \054 0	 0 1016 int8in int8out int8recv int8send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("~18 digit integer, 8-byte storage");
  #define INT8OID			20
  
! DATA(insert OID = 21 (	int2	   PGNSP PGUID	2 t b N f t \054 0	 0 1005 int2in int2out int2recv int2send - - - s p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("-32 thousand to 32 thousand, 2-byte storage");
  #define INT2OID			21
  
! DATA(insert OID = 22 (	int2vector PGNSP PGUID -1 f b A f t \054 0	21 1006 int2vectorin int2vectorout int2vectorrecv int2vectorsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("array of int2, used in system tables");
  #define INT2VECTOROID	22
  
! DATA(insert OID = 23 (	int4	   PGNSP PGUID	4 t b N f t \054 0	 0 1007 int4in int4out int4recv int4send - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("-2 billion to 2 billion integer, 4-byte storage");
  #define INT4OID			23
  
! DATA(insert OID = 24 (	regproc    PGNSP PGUID	4 t b N f t \054 0	 0 1008 regprocin regprocout regprocrecv regprocsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered procedure");
  #define REGPROCOID		24
  
! DATA(insert OID = 25 (	text	   PGNSP PGUID -1 f b S t t \054 0	0 1009 textin textout textrecv textsend - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("variable-length string, no limit specified");
  #define TEXTOID			25
  
! DATA(insert OID = 26 (	oid		   PGNSP PGUID	4 t b N t t \054 0	 0 1028 oidin oidout oidrecv oidsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("object identifier(oid), maximum 4 billion");
  #define OIDOID			26
  
! DATA(insert OID = 27 (	tid		   PGNSP PGUID	6 f b U f t \054 0	 0 1010 tidin tidout tidrecv tidsend - - - s p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("(block, offset), physical location of tuple");
  #define TIDOID		27
  
! DATA(insert OID = 28 (	xid		   PGNSP PGUID	4 t b U f t \054 0	 0 1011 xidin xidout xidrecv xidsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("transaction id");
  #define XIDOID 28
  
! DATA(insert OID = 29 (	cid		   PGNSP PGUID	4 t b U f t \054 0	 0 1012 cidin cidout cidrecv cidsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("command identifier type, sequence in transaction id");
  #define CIDOID 29
  
! DATA(insert OID = 30 (	oidvector  PGNSP PGUID -1 f b A f t \054 0	26 1013 oidvectorin oidvectorout oidvectorrecv oidvectorsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("array of oids, used in system tables");
  #define OIDVECTOROID	30
  
  /* hand-built rowtype entries for bootstrapped catalogs */
  /* NB: OIDs assigned here must match the BKI_ROWTYPE_OID declarations */
  
! DATA(insert OID = 71 (	pg_type			PGNSP PGUID -1 f c C f t \054 1247 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 75 (	pg_attribute	PGNSP PGUID -1 f c C f t \054 1249 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 81 (	pg_proc			PGNSP PGUID -1 f c C f t \054 1255 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 83 (	pg_class		PGNSP PGUID -1 f c C f t \054 1259 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 100 - 199 */
! DATA(insert OID = 142 ( xml		   PGNSP PGUID -1 f b U f t \054 0 0 143 xml_in xml_out xml_recv xml_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("XML content");
  #define XMLOID 142
! DATA(insert OID = 143 ( _xml	   PGNSP PGUID -1 f b A f t \054 0 142 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  
! DATA(insert OID = 194 (	pg_node_tree	PGNSP PGUID -1 f b S f t \054 0	0 0 pg_node_tree_in pg_node_tree_out pg_node_tree_recv pg_node_tree_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("string representing an internal node tree");
  #define PGNODETREEOID	194
  
  /* OIDS 200 - 299 */
  
! DATA(insert OID = 210 (  smgr	   PGNSP PGUID 2 t b U f t \054 0 0 0 smgrin smgrout - - - - - s p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("storage manager");
  
  /* OIDS 300 - 399 */
***************
*** 359,589 **** DESCR("storage manager");
  /* OIDS 500 - 599 */
  
  /* OIDS 600 - 699 */
! DATA(insert OID = 600 (  point	   PGNSP PGUID 16 f b G f t \054 0 701 1017 point_in point_out point_recv point_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("geometric point '(x, y)'");
  #define POINTOID		600
! DATA(insert OID = 601 (  lseg	   PGNSP PGUID 32 f b G f t \054 0 600 1018 lseg_in lseg_out lseg_recv lseg_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("geometric line segment '(pt1,pt2)'");
  #define LSEGOID			601
! DATA(insert OID = 602 (  path	   PGNSP PGUID -1 f b G f t \054 0 0 1019 path_in path_out path_recv path_send - - - d x f 0 -1 0 _null_ _null_ ));
  DESCR("geometric path '(pt1,...)'");
  #define PATHOID			602
! DATA(insert OID = 603 (  box	   PGNSP PGUID 32 f b G f t \073 0 600 1020 box_in box_out box_recv box_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("geometric box '(lower left,upper right)'");
  #define BOXOID			603
! DATA(insert OID = 604 (  polygon   PGNSP PGUID -1 f b G f t \054 0	 0 1027 poly_in poly_out poly_recv poly_send - - - d x f 0 -1 0 _null_ _null_ ));
  DESCR("geometric polygon '(pt1,...)'");
  #define POLYGONOID		604
  
! DATA(insert OID = 628 (  line	   PGNSP PGUID 32 f b G f t \054 0 701 629 line_in line_out line_recv line_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("geometric line (not implemented)");
  #define LINEOID			628
! DATA(insert OID = 629 (  _line	   PGNSP PGUID	-1 f b A f t \054 0 628 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
  DESCR("");
  
  /* OIDS 700 - 799 */
  
! DATA(insert OID = 700 (  float4    PGNSP PGUID	4 FLOAT4PASSBYVAL b N f t \054 0	 0 1021 float4in float4out float4recv float4send - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("single-precision floating point number, 4-byte storage");
  #define FLOAT4OID 700
! DATA(insert OID = 701 (  float8    PGNSP PGUID	8 FLOAT8PASSBYVAL b N t t \054 0	 0 1022 float8in float8out float8recv float8send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("double-precision floating point number, 8-byte storage");
  #define FLOAT8OID 701
! DATA(insert OID = 702 (  abstime   PGNSP PGUID	4 t b D f t \054 0	 0 1023 abstimein abstimeout abstimerecv abstimesend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("absolute, limited-range date and time (Unix system time)");
  #define ABSTIMEOID		702
! DATA(insert OID = 703 (  reltime   PGNSP PGUID	4 t b T f t \054 0	 0 1024 reltimein reltimeout reltimerecv reltimesend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("relative, limited-range time interval (Unix delta time)");
  #define RELTIMEOID		703
! DATA(insert OID = 704 (  tinterval PGNSP PGUID 12 f b T f t \054 0	 0 1025 tintervalin tintervalout tintervalrecv tintervalsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("(abstime,abstime), time interval");
  #define TINTERVALOID	704
! DATA(insert OID = 705 (  unknown   PGNSP PGUID -2 f b X f t \054 0	 0 0 unknownin unknownout unknownrecv unknownsend - - - c p f 0 -1 0 _null_ _null_ ));
  DESCR("");
  #define UNKNOWNOID		705
  
! DATA(insert OID = 718 (  circle    PGNSP PGUID	24 f b G f t \054 0 0 719 circle_in circle_out circle_recv circle_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("geometric circle '(center,radius)'");
  #define CIRCLEOID		718
! DATA(insert OID = 719 (  _circle   PGNSP PGUID	-1 f b A f t \054 0  718 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 790 (  money	   PGNSP PGUID	 8 FLOAT8PASSBYVAL b N f t \054 0 0 791 cash_in cash_out cash_recv cash_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("monetary amounts, $d,ddd.cc");
  #define CASHOID 790
! DATA(insert OID = 791 (  _money    PGNSP PGUID	-1 f b A f t \054 0  790 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 800 - 899 */
! DATA(insert OID = 829 ( macaddr    PGNSP PGUID	6 f b U f t \054 0 0 1040 macaddr_in macaddr_out macaddr_recv macaddr_send - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("XX:XX:XX:XX:XX:XX, MAC address");
  #define MACADDROID 829
! DATA(insert OID = 869 ( inet	   PGNSP PGUID	-1 f b I t t \054 0 0 1041 inet_in inet_out inet_recv inet_send - - - i m f 0 -1 0 _null_ _null_ ));
  DESCR("IP address/netmask, host address, netmask optional");
  #define INETOID 869
! DATA(insert OID = 650 ( cidr	   PGNSP PGUID	-1 f b I f t \054 0 0 651 cidr_in cidr_out cidr_recv cidr_send - - - i m f 0 -1 0 _null_ _null_ ));
  DESCR("network IP address/netmask, network address");
  #define CIDROID 650
  
  /* OIDS 900 - 999 */
  
  /* OIDS 1000 - 1099 */
! DATA(insert OID = 1000 (  _bool		 PGNSP PGUID -1 f b A f t \054 0	16 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1001 (  _bytea	 PGNSP PGUID -1 f b A f t \054 0	17 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1002 (  _char		 PGNSP PGUID -1 f b A f t \054 0	18 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1003 (  _name		 PGNSP PGUID -1 f b A f t \054 0	19 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1005 (  _int2		 PGNSP PGUID -1 f b A f t \054 0	21 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1006 (  _int2vector PGNSP PGUID -1 f b A f t \054 0	22 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1007 (  _int4		 PGNSP PGUID -1 f b A f t \054 0	23 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  #define INT4ARRAYOID		1007
! DATA(insert OID = 1008 (  _regproc	 PGNSP PGUID -1 f b A f t \054 0	24 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1009 (  _text		 PGNSP PGUID -1 f b A f t \054 0	25 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  #define TEXTARRAYOID		1009
! DATA(insert OID = 1028 (  _oid		 PGNSP PGUID -1 f b A f t \054 0	26 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1010 (  _tid		 PGNSP PGUID -1 f b A f t \054 0	27 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1011 (  _xid		 PGNSP PGUID -1 f b A f t \054 0	28 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1012 (  _cid		 PGNSP PGUID -1 f b A f t \054 0	29 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1013 (  _oidvector PGNSP PGUID -1 f b A f t \054 0	30 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1014 (  _bpchar	 PGNSP PGUID -1 f b A f t \054 0 1042 0 array_in array_out array_recv array_send bpchartypmodin bpchartypmodout - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1015 (  _varchar	 PGNSP PGUID -1 f b A f t \054 0 1043 0 array_in array_out array_recv array_send varchartypmodin varchartypmodout - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1016 (  _int8		 PGNSP PGUID -1 f b A f t \054 0	20 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1017 (  _point	 PGNSP PGUID -1 f b A f t \054 0 600 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1018 (  _lseg		 PGNSP PGUID -1 f b A f t \054 0 601 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1019 (  _path		 PGNSP PGUID -1 f b A f t \054 0 602 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1020 (  _box		 PGNSP PGUID -1 f b A f t \073 0 603 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1021 (  _float4	 PGNSP PGUID -1 f b A f t \054 0 700 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  #define FLOAT4ARRAYOID 1021
! DATA(insert OID = 1022 (  _float8	 PGNSP PGUID -1 f b A f t \054 0 701 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1023 (  _abstime	 PGNSP PGUID -1 f b A f t \054 0 702 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1024 (  _reltime	 PGNSP PGUID -1 f b A f t \054 0 703 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1025 (  _tinterval PGNSP PGUID -1 f b A f t \054 0 704 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1027 (  _polygon	 PGNSP PGUID -1 f b A f t \054 0 604 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1033 (  aclitem	 PGNSP PGUID 12 f b U f t \054 0 0 1034 aclitemin aclitemout - - - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("access control list");
  #define ACLITEMOID		1033
! DATA(insert OID = 1034 (  _aclitem	 PGNSP PGUID -1 f b A f t \054 0 1033 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1040 (  _macaddr	 PGNSP PGUID -1 f b A f t \054 0  829 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1041 (  _inet		 PGNSP PGUID -1 f b A f t \054 0  869 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 651  (  _cidr		 PGNSP PGUID -1 f b A f t \054 0  650 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1263 (  _cstring	 PGNSP PGUID -1 f b A f t \054 0 2275 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  #define CSTRINGARRAYOID		1263
  
! DATA(insert OID = 1042 ( bpchar		 PGNSP PGUID -1 f b S f t \054 0	0 1014 bpcharin bpcharout bpcharrecv bpcharsend bpchartypmodin bpchartypmodout - i x f 0 -1 0 _null_ _null_ ));
  DESCR("char(length), blank-padded string, fixed storage length");
  #define BPCHAROID		1042
! DATA(insert OID = 1043 ( varchar	 PGNSP PGUID -1 f b S f t \054 0	0 1015 varcharin varcharout varcharrecv varcharsend varchartypmodin varchartypmodout - i x f 0 -1 0 _null_ _null_ ));
  DESCR("varchar(length), non-blank-padded string, variable storage length");
  #define VARCHAROID		1043
  
! DATA(insert OID = 1082 ( date		 PGNSP PGUID	4 t b D f t \054 0	0 1182 date_in date_out date_recv date_send - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("date");
  #define DATEOID			1082
! DATA(insert OID = 1083 ( time		 PGNSP PGUID	8 FLOAT8PASSBYVAL b D f t \054 0	0 1183 time_in time_out time_recv time_send timetypmodin timetypmodout - d p f 0 -1 0 _null_ _null_ ));
  DESCR("time of day");
  #define TIMEOID			1083
  
  /* OIDS 1100 - 1199 */
! DATA(insert OID = 1114 ( timestamp	 PGNSP PGUID	8 FLOAT8PASSBYVAL b D f t \054 0	0 1115 timestamp_in timestamp_out timestamp_recv timestamp_send timestamptypmodin timestamptypmodout - d p f 0 -1 0 _null_ _null_ ));
  DESCR("date and time");
  #define TIMESTAMPOID	1114
! DATA(insert OID = 1115 ( _timestamp  PGNSP PGUID	-1 f b A f t \054 0 1114 0 array_in array_out array_recv array_send timestamptypmodin timestamptypmodout - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1182 ( _date		 PGNSP PGUID	-1 f b A f t \054 0 1082 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1183 ( _time		 PGNSP PGUID	-1 f b A f t \054 0 1083 0 array_in array_out array_recv array_send timetypmodin timetypmodout - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1184 ( timestamptz PGNSP PGUID	8 FLOAT8PASSBYVAL b D t t \054 0	0 1185 timestamptz_in timestamptz_out timestamptz_recv timestamptz_send timestamptztypmodin timestamptztypmodout - d p f 0 -1 0 _null_ _null_ ));
  DESCR("date and time with time zone");
  #define TIMESTAMPTZOID	1184
! DATA(insert OID = 1185 ( _timestamptz PGNSP PGUID -1 f b A f t \054 0	1184 0 array_in array_out array_recv array_send timestamptztypmodin timestamptztypmodout - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1186 ( interval	 PGNSP PGUID 16 f b T t t \054 0	0 1187 interval_in interval_out interval_recv interval_send intervaltypmodin intervaltypmodout - d p f 0 -1 0 _null_ _null_ ));
  DESCR("@ <number> <units>, time interval");
  #define INTERVALOID		1186
! DATA(insert OID = 1187 ( _interval	 PGNSP PGUID	-1 f b A f t \054 0 1186 0 array_in array_out array_recv array_send intervaltypmodin intervaltypmodout - d x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 1200 - 1299 */
! DATA(insert OID = 1231 (  _numeric	 PGNSP PGUID -1 f b A f t \054 0	1700 0 array_in array_out array_recv array_send numerictypmodin numerictypmodout - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1266 ( timetz		 PGNSP PGUID 12 f b D f t \054 0	0 1270 timetz_in timetz_out timetz_recv timetz_send timetztypmodin timetztypmodout - d p f 0 -1 0 _null_ _null_ ));
  DESCR("time of day with time zone");
  #define TIMETZOID		1266
! DATA(insert OID = 1270 ( _timetz	 PGNSP PGUID -1 f b A f t \054 0	1266 0 array_in array_out array_recv array_send timetztypmodin timetztypmodout - d x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 1500 - 1599 */
! DATA(insert OID = 1560 ( bit		 PGNSP PGUID -1 f b V f t \054 0	0 1561 bit_in bit_out bit_recv bit_send bittypmodin bittypmodout - i x f 0 -1 0 _null_ _null_ ));
  DESCR("fixed-length bit string");
  #define BITOID	 1560
! DATA(insert OID = 1561 ( _bit		 PGNSP PGUID -1 f b A f t \054 0	1560 0 array_in array_out array_recv array_send bittypmodin bittypmodout - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1562 ( varbit		 PGNSP PGUID -1 f b V t t \054 0	0 1563 varbit_in varbit_out varbit_recv varbit_send varbittypmodin varbittypmodout - i x f 0 -1 0 _null_ _null_ ));
  DESCR("variable-length bit string");
  #define VARBITOID	  1562
! DATA(insert OID = 1563 ( _varbit	 PGNSP PGUID -1 f b A f t \054 0	1562 0 array_in array_out array_recv array_send varbittypmodin varbittypmodout - i x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 1600 - 1699 */
  
  /* OIDS 1700 - 1799 */
! DATA(insert OID = 1700 ( numeric	   PGNSP PGUID -1 f b N f t \054 0	0 1231 numeric_in numeric_out numeric_recv numeric_send numerictypmodin numerictypmodout - i m f 0 -1 0 _null_ _null_ ));
  DESCR("numeric(precision, decimal), arbitrary precision number");
  #define NUMERICOID		1700
  
! DATA(insert OID = 1790 ( refcursor	   PGNSP PGUID -1 f b U f t \054 0	0 2201 textin textout textrecv textsend - - - i x f 0 -1 0 _null_ _null_ ));
  DESCR("reference to cursor (portal name)");
  #define REFCURSOROID	1790
  
  /* OIDS 2200 - 2299 */
! DATA(insert OID = 2201 ( _refcursor    PGNSP PGUID -1 f b A f t \054 0 1790 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  
! DATA(insert OID = 2202 ( regprocedure  PGNSP PGUID	4 t b N f t \054 0	 0 2207 regprocedurein regprocedureout regprocedurerecv regproceduresend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered procedure (with args)");
  #define REGPROCEDUREOID 2202
  
! DATA(insert OID = 2203 ( regoper	   PGNSP PGUID	4 t b N f t \054 0	 0 2208 regoperin regoperout regoperrecv regopersend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered operator");
  #define REGOPEROID		2203
  
! DATA(insert OID = 2204 ( regoperator   PGNSP PGUID	4 t b N f t \054 0	 0 2209 regoperatorin regoperatorout regoperatorrecv regoperatorsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered operator (with args)");
  #define REGOPERATOROID	2204
  
! DATA(insert OID = 2205 ( regclass	   PGNSP PGUID	4 t b N f t \054 0	 0 2210 regclassin regclassout regclassrecv regclasssend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered class");
  #define REGCLASSOID		2205
  
! DATA(insert OID = 2206 ( regtype	   PGNSP PGUID	4 t b N f t \054 0	 0 2211 regtypein regtypeout regtyperecv regtypesend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered type");
  #define REGTYPEOID		2206
  
! DATA(insert OID = 2207 ( _regprocedure PGNSP PGUID -1 f b A f t \054 0 2202 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 2208 ( _regoper	   PGNSP PGUID -1 f b A f t \054 0 2203 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 2209 ( _regoperator  PGNSP PGUID -1 f b A f t \054 0 2204 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 2210 ( _regclass	   PGNSP PGUID -1 f b A f t \054 0 2205 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 2211 ( _regtype	   PGNSP PGUID -1 f b A f t \054 0 2206 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  #define REGTYPEARRAYOID 2211
  
  /* uuid */
! DATA(insert OID = 2950 ( uuid			PGNSP PGUID 16 f b U f t \054 0 0 2951 uuid_in uuid_out uuid_recv uuid_send - - - c p f 0 -1 0 _null_ _null_ ));
  DESCR("UUID datatype");
! DATA(insert OID = 2951 ( _uuid			PGNSP PGUID -1 f b A f t \054 0 2950 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  
  /* text search */
! DATA(insert OID = 3614 ( tsvector		PGNSP PGUID -1 f b U f t \054 0 0 3643 tsvectorin tsvectorout tsvectorrecv tsvectorsend - - ts_typanalyze i x f 0 -1 0 _null_ _null_ ));
  DESCR("text representation for text search");
  #define TSVECTOROID		3614
! DATA(insert OID = 3642 ( gtsvector		PGNSP PGUID -1 f b U f t \054 0 0 3644 gtsvectorin gtsvectorout - - - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("GiST index internal text representation for text search");
  #define GTSVECTOROID	3642
! DATA(insert OID = 3615 ( tsquery		PGNSP PGUID -1 f b U f t \054 0 0 3645 tsqueryin tsqueryout tsqueryrecv tsquerysend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("query representation for text search");
  #define TSQUERYOID		3615
! DATA(insert OID = 3734 ( regconfig		PGNSP PGUID 4 t b N f t \054 0 0 3735 regconfigin regconfigout regconfigrecv regconfigsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered text search configuration");
  #define REGCONFIGOID	3734
! DATA(insert OID = 3769 ( regdictionary	PGNSP PGUID 4 t b N f t \054 0 0 3770 regdictionaryin regdictionaryout regdictionaryrecv regdictionarysend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered text search dictionary");
  #define REGDICTIONARYOID	3769
  
! DATA(insert OID = 3643 ( _tsvector		PGNSP PGUID -1 f b A f t \054 0 3614 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 3644 ( _gtsvector		PGNSP PGUID -1 f b A f t \054 0 3642 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 3645 ( _tsquery		PGNSP PGUID -1 f b A f t \054 0 3615 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 3735 ( _regconfig		PGNSP PGUID -1 f b A f t \054 0 3734 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 3770 ( _regdictionary PGNSP PGUID -1 f b A f t \054 0 3769 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  
! DATA(insert OID = 2970 ( txid_snapshot	PGNSP PGUID -1 f b U f t \054 0 0 2949 txid_snapshot_in txid_snapshot_out txid_snapshot_recv txid_snapshot_send - - - d x f 0 -1 0 _null_ _null_ ));
  DESCR("txid snapshot");
! DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
  
  /*
   * pseudo-types
--- 373,603 ----
  /* OIDS 500 - 599 */
  
  /* OIDS 600 - 699 */
! DATA(insert OID = 600 (  point	   PGNSP PGUID 16 f b G f t \054 0 701 1017 point_in point_out point_recv point_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric point '(x, y)'");
  #define POINTOID		600
! DATA(insert OID = 601 (  lseg	   PGNSP PGUID 32 f b G f t \054 0 600 1018 lseg_in lseg_out lseg_recv lseg_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric line segment '(pt1,pt2)'");
  #define LSEGOID			601
! DATA(insert OID = 602 (  path	   PGNSP PGUID -1 f b G f t \054 0 0 1019 path_in path_out path_recv path_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric path '(pt1,...)'");
  #define PATHOID			602
! DATA(insert OID = 603 (  box	   PGNSP PGUID 32 f b G f t \073 0 600 1020 box_in box_out box_recv box_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric box '(lower left,upper right)'");
  #define BOXOID			603
! DATA(insert OID = 604 (  polygon   PGNSP PGUID -1 f b G f t \054 0	 0 1027 poly_in poly_out poly_recv poly_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric polygon '(pt1,...)'");
  #define POLYGONOID		604
  
! DATA(insert OID = 628 (  line	   PGNSP PGUID 32 f b G f t \054 0 701 629 line_in line_out line_recv line_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric line (not implemented)");
  #define LINEOID			628
! DATA(insert OID = 629 (  _line	   PGNSP PGUID	-1 f b A f t \054 0 628 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("");
  
  /* OIDS 700 - 799 */
  
! DATA(insert OID = 700 (  float4    PGNSP PGUID	4 FLOAT4PASSBYVAL b N f t \054 0	 0 1021 float4in float4out float4recv float4send - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("single-precision floating point number, 4-byte storage");
  #define FLOAT4OID 700
! DATA(insert OID = 701 (  float8    PGNSP PGUID	8 FLOAT8PASSBYVAL b N t t \054 0	 0 1022 float8in float8out float8recv float8send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("double-precision floating point number, 8-byte storage");
  #define FLOAT8OID 701
! DATA(insert OID = 702 (  abstime   PGNSP PGUID	4 t b D f t \054 0	 0 1023 abstimein abstimeout abstimerecv abstimesend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("absolute, limited-range date and time (Unix system time)");
  #define ABSTIMEOID		702
! DATA(insert OID = 703 (  reltime   PGNSP PGUID	4 t b T f t \054 0	 0 1024 reltimein reltimeout reltimerecv reltimesend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("relative, limited-range time interval (Unix delta time)");
  #define RELTIMEOID		703
! DATA(insert OID = 704 (  tinterval PGNSP PGUID 12 f b T f t \054 0	 0 1025 tintervalin tintervalout tintervalrecv tintervalsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("(abstime,abstime), time interval");
  #define TINTERVALOID	704
! DATA(insert OID = 705 (  unknown   PGNSP PGUID -2 f b X f t \054 0	 0 0 unknownin unknownout unknownrecv unknownsend - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("");
  #define UNKNOWNOID		705
  
! DATA(insert OID = 718 (  circle    PGNSP PGUID	24 f b G f t \054 0 0 719 circle_in circle_out circle_recv circle_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric circle '(center,radius)'");
  #define CIRCLEOID		718
! DATA(insert OID = 719 (  _circle   PGNSP PGUID	-1 f b A f t \054 0  718 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 790 (  money	   PGNSP PGUID	 8 FLOAT8PASSBYVAL b N f t \054 0 0 791 cash_in cash_out cash_recv cash_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("monetary amounts, $d,ddd.cc");
  #define CASHOID 790
! DATA(insert OID = 791 (  _money    PGNSP PGUID	-1 f b A f t \054 0  790 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 800 - 899 */
! DATA(insert OID = 829 ( macaddr    PGNSP PGUID	6 f b U f t \054 0 0 1040 macaddr_in macaddr_out macaddr_recv macaddr_send - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("XX:XX:XX:XX:XX:XX, MAC address");
  #define MACADDROID 829
! DATA(insert OID = 869 ( inet	   PGNSP PGUID	-1 f b I t t \054 0 0 1041 inet_in inet_out inet_recv inet_send - - - i m f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("IP address/netmask, host address, netmask optional");
  #define INETOID 869
! DATA(insert OID = 650 ( cidr	   PGNSP PGUID	-1 f b I f t \054 0 0 651 cidr_in cidr_out cidr_recv cidr_send - - - i m f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("network IP address/netmask, network address");
  #define CIDROID 650
  
  /* OIDS 900 - 999 */
  
  /* OIDS 1000 - 1099 */
! DATA(insert OID = 1000 (  _bool		 PGNSP PGUID -1 f b A f t \054 0	16 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1001 (  _bytea	 PGNSP PGUID -1 f b A f t \054 0	17 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1002 (  _char		 PGNSP PGUID -1 f b A f t \054 0	18 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1003 (  _name		 PGNSP PGUID -1 f b A f t \054 0	19 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1005 (  _int2		 PGNSP PGUID -1 f b A f t \054 0	21 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1006 (  _int2vector PGNSP PGUID -1 f b A f t \054 0	22 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1007 (  _int4		 PGNSP PGUID -1 f b A f t \054 0	23 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  #define INT4ARRAYOID		1007
! DATA(insert OID = 1008 (  _regproc	 PGNSP PGUID -1 f b A f t \054 0	24 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1009 (  _text		 PGNSP PGUID -1 f b A f t \054 0	25 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  #define TEXTARRAYOID		1009
! DATA(insert OID = 1028 (  _oid		 PGNSP PGUID -1 f b A f t \054 0	26 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1010 (  _tid		 PGNSP PGUID -1 f b A f t \054 0	27 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1011 (  _xid		 PGNSP PGUID -1 f b A f t \054 0	28 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1012 (  _cid		 PGNSP PGUID -1 f b A f t \054 0	29 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1013 (  _oidvector PGNSP PGUID -1 f b A f t \054 0	30 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1014 (  _bpchar	 PGNSP PGUID -1 f b A f t \054 0 1042 0 array_in array_out array_recv array_send bpchartypmodin bpchartypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1015 (  _varchar	 PGNSP PGUID -1 f b A f t \054 0 1043 0 array_in array_out array_recv array_send varchartypmodin varchartypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1016 (  _int8		 PGNSP PGUID -1 f b A f t \054 0	20 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1017 (  _point	 PGNSP PGUID -1 f b A f t \054 0 600 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1018 (  _lseg		 PGNSP PGUID -1 f b A f t \054 0 601 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1019 (  _path		 PGNSP PGUID -1 f b A f t \054 0 602 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1020 (  _box		 PGNSP PGUID -1 f b A f t \073 0 603 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1021 (  _float4	 PGNSP PGUID -1 f b A f t \054 0 700 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  #define FLOAT4ARRAYOID 1021
! DATA(insert OID = 1022 (  _float8	 PGNSP PGUID -1 f b A f t \054 0 701 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1023 (  _abstime	 PGNSP PGUID -1 f b A f t \054 0 702 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1024 (  _reltime	 PGNSP PGUID -1 f b A f t \054 0 703 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1025 (  _tinterval PGNSP PGUID -1 f b A f t \054 0 704 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1027 (  _polygon	 PGNSP PGUID -1 f b A f t \054 0 604 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1033 (  aclitem	 PGNSP PGUID 12 f b U f t \054 0 0 1034 aclitemin aclitemout - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("access control list");
  #define ACLITEMOID		1033
! DATA(insert OID = 1034 (  _aclitem	 PGNSP PGUID -1 f b A f t \054 0 1033 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1040 (  _macaddr	 PGNSP PGUID -1 f b A f t \054 0  829 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1041 (  _inet		 PGNSP PGUID -1 f b A f t \054 0  869 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 651  (  _cidr		 PGNSP PGUID -1 f b A f t \054 0  650 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1263 (  _cstring	 PGNSP PGUID -1 f b A f t \054 0 2275 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  #define CSTRINGARRAYOID		1263
  
! DATA(insert OID = 1042 ( bpchar		 PGNSP PGUID -1 f b S f t \054 0	0 1014 bpcharin bpcharout bpcharrecv bpcharsend bpchartypmodin bpchartypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("char(length), blank-padded string, fixed storage length");
  #define BPCHAROID		1042
! DATA(insert OID = 1043 ( varchar	 PGNSP PGUID -1 f b S f t \054 0	0 1015 varcharin varcharout varcharrecv varcharsend varchartypmodin varchartypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("varchar(length), non-blank-padded string, variable storage length");
  #define VARCHAROID		1043
  
! DATA(insert OID = 1082 ( date		 PGNSP PGUID	4 t b D f t \054 0	0 1182 date_in date_out date_recv date_send - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("date");
  #define DATEOID			1082
! DATA(insert OID = 1083 ( time		 PGNSP PGUID	8 FLOAT8PASSBYVAL b D f t \054 0	0 1183 time_in time_out time_recv time_send timetypmodin timetypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("time of day");
  #define TIMEOID			1083
  
  /* OIDS 1100 - 1199 */
! DATA(insert OID = 1114 ( timestamp	 PGNSP PGUID	8 FLOAT8PASSBYVAL b D f t \054 0	0 1115 timestamp_in timestamp_out timestamp_recv timestamp_send timestamptypmodin timestamptypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("date and time");
  #define TIMESTAMPOID	1114
! DATA(insert OID = 1115 ( _timestamp  PGNSP PGUID	-1 f b A f t \054 0 1114 0 array_in array_out array_recv array_send timestamptypmodin timestamptypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1182 ( _date		 PGNSP PGUID	-1 f b A f t \054 0 1082 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1183 ( _time		 PGNSP PGUID	-1 f b A f t \054 0 1083 0 array_in array_out array_recv array_send timetypmodin timetypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1184 ( timestamptz PGNSP PGUID	8 FLOAT8PASSBYVAL b D t t \054 0	0 1185 timestamptz_in timestamptz_out timestamptz_recv timestamptz_send timestamptztypmodin timestamptztypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("date and time with time zone");
  #define TIMESTAMPTZOID	1184
! DATA(insert OID = 1185 ( _timestamptz PGNSP PGUID -1 f b A f t \054 0	1184 0 array_in array_out array_recv array_send timestamptztypmodin timestamptztypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1186 ( interval	 PGNSP PGUID 16 f b T t t \054 0	0 1187 interval_in interval_out interval_recv interval_send intervaltypmodin intervaltypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("@ <number> <units>, time interval");
  #define INTERVALOID		1186
! DATA(insert OID = 1187 ( _interval	 PGNSP PGUID	-1 f b A f t \054 0 1186 0 array_in array_out array_recv array_send intervaltypmodin intervaltypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 1200 - 1299 */
! DATA(insert OID = 1231 (  _numeric	 PGNSP PGUID -1 f b A f t \054 0	1700 0 array_in array_out array_recv array_send numerictypmodin numerictypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1266 ( timetz		 PGNSP PGUID 12 f b D f t \054 0	0 1270 timetz_in timetz_out timetz_recv timetz_send timetztypmodin timetztypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("time of day with time zone");
  #define TIMETZOID		1266
! DATA(insert OID = 1270 ( _timetz	 PGNSP PGUID -1 f b A f t \054 0	1266 0 array_in array_out array_recv array_send timetztypmodin timetztypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 1500 - 1599 */
! DATA(insert OID = 1560 ( bit		 PGNSP PGUID -1 f b V f t \054 0	0 1561 bit_in bit_out bit_recv bit_send bittypmodin bittypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("fixed-length bit string");
  #define BITOID	 1560
! DATA(insert OID = 1561 ( _bit		 PGNSP PGUID -1 f b A f t \054 0	1560 0 array_in array_out array_recv array_send bittypmodin bittypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1562 ( varbit		 PGNSP PGUID -1 f b V t t \054 0	0 1563 varbit_in varbit_out varbit_recv varbit_send varbittypmodin varbittypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("variable-length bit string");
  #define VARBITOID	  1562
! DATA(insert OID = 1563 ( _varbit	 PGNSP PGUID -1 f b A f t \054 0	1562 0 array_in array_out array_recv array_send varbittypmodin varbittypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 1600 - 1699 */
  
  /* OIDS 1700 - 1799 */
! DATA(insert OID = 1700 ( numeric	   PGNSP PGUID -1 f b N f t \054 0	0 1231 numeric_in numeric_out numeric_recv numeric_send numerictypmodin numerictypmodout - i m f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("numeric(precision, decimal), arbitrary precision number");
  #define NUMERICOID		1700
  
! DATA(insert OID = 1790 ( refcursor	   PGNSP PGUID -1 f b U f t \054 0	0 2201 textin textout textrecv textsend - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("reference to cursor (portal name)");
  #define REFCURSOROID	1790
  
  /* OIDS 2200 - 2299 */
! DATA(insert OID = 2201 ( _refcursor    PGNSP PGUID -1 f b A f t \054 0 1790 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  
! DATA(insert OID = 2202 ( regprocedure  PGNSP PGUID	4 t b N f t \054 0	 0 2207 regprocedurein regprocedureout regprocedurerecv regproceduresend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered procedure (with args)");
  #define REGPROCEDUREOID 2202
  
! DATA(insert OID = 2203 ( regoper	   PGNSP PGUID	4 t b N f t \054 0	 0 2208 regoperin regoperout regoperrecv regopersend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered operator");
  #define REGOPEROID		2203
  
! DATA(insert OID = 2204 ( regoperator   PGNSP PGUID	4 t b N f t \054 0	 0 2209 regoperatorin regoperatorout regoperatorrecv regoperatorsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered operator (with args)");
  #define REGOPERATOROID	2204
  
! DATA(insert OID = 2205 ( regclass	   PGNSP PGUID	4 t b N f t \054 0	 0 2210 regclassin regclassout regclassrecv regclasssend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered class");
  #define REGCLASSOID		2205
  
! DATA(insert OID = 2206 ( regtype	   PGNSP PGUID	4 t b N f t \054 0	 0 2211 regtypein regtypeout regtyperecv regtypesend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered type");
  #define REGTYPEOID		2206
  
! DATA(insert OID = 2207 ( _regprocedure PGNSP PGUID -1 f b A f t \054 0 2202 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 2208 ( _regoper	   PGNSP PGUID -1 f b A f t \054 0 2203 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 2209 ( _regoperator  PGNSP PGUID -1 f b A f t \054 0 2204 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 2210 ( _regclass	   PGNSP PGUID -1 f b A f t \054 0 2205 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 2211 ( _regtype	   PGNSP PGUID -1 f b A f t \054 0 2206 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  #define REGTYPEARRAYOID 2211
  
  /* uuid */
! DATA(insert OID = 2950 ( uuid			PGNSP PGUID 16 f b U f t \054 0 0 2951 uuid_in uuid_out uuid_recv uuid_send - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("UUID datatype");
! DATA(insert OID = 2951 ( _uuid			PGNSP PGUID -1 f b A f t \054 0 2950 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* text search */
! DATA(insert OID = 3614 ( tsvector		PGNSP PGUID -1 f b U f t \054 0 0 3643 tsvectorin tsvectorout tsvectorrecv tsvectorsend - - ts_typanalyze i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("text representation for text search");
  #define TSVECTOROID		3614
! DATA(insert OID = 3642 ( gtsvector		PGNSP PGUID -1 f b U f t \054 0 0 3644 gtsvectorin gtsvectorout - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("GiST index internal text representation for text search");
  #define GTSVECTOROID	3642
! DATA(insert OID = 3615 ( tsquery		PGNSP PGUID -1 f b U f t \054 0 0 3645 tsqueryin tsqueryout tsqueryrecv tsquerysend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("query representation for text search");
  #define TSQUERYOID		3615
! DATA(insert OID = 3734 ( regconfig		PGNSP PGUID 4 t b N f t \054 0 0 3735 regconfigin regconfigout regconfigrecv regconfigsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered text search configuration");
  #define REGCONFIGOID	3734
! DATA(insert OID = 3769 ( regdictionary	PGNSP PGUID 4 t b N f t \054 0 0 3770 regdictionaryin regdictionaryout regdictionaryrecv regdictionarysend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered text search dictionary");
  #define REGDICTIONARYOID	3769
  
! DATA(insert OID = 3643 ( _tsvector		PGNSP PGUID -1 f b A f t \054 0 3614 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 3644 ( _gtsvector		PGNSP PGUID -1 f b A f t \054 0 3642 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 3645 ( _tsquery		PGNSP PGUID -1 f b A f t \054 0 3615 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 3735 ( _regconfig		PGNSP PGUID -1 f b A f t \054 0 3734 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 3770 ( _regdictionary PGNSP PGUID -1 f b A f t \054 0 3769 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  
! DATA(insert OID = 2970 ( txid_snapshot	PGNSP PGUID -1 f b U f t \054 0 0 2949 txid_snapshot_in txid_snapshot_out txid_snapshot_recv txid_snapshot_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("txid snapshot");
! DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  
  /*
   * pseudo-types
***************
*** 598,628 **** DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 a
   * but there is now support for it in records and arrays.  Perhaps we should
   * just treat it as a regular base type?
   */
! DATA(insert OID = 2249 ( record			PGNSP PGUID -1 f p P f t \054 0 0 2287 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
  #define RECORDOID		2249
! DATA(insert OID = 2287 ( _record		PGNSP PGUID -1 f p P f t \054 0 2249 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
  #define RECORDARRAYOID	2287
! DATA(insert OID = 2275 ( cstring		PGNSP PGUID -2 f p P f t \054 0 0 1263 cstring_in cstring_out cstring_recv cstring_send - - - c p f 0 -1 0 _null_ _null_ ));
  #define CSTRINGOID		2275
! DATA(insert OID = 2276 ( any			PGNSP PGUID  4 t p P f t \054 0 0 0 any_in any_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define ANYOID			2276
! DATA(insert OID = 2277 ( anyarray		PGNSP PGUID -1 f p P f t \054 0 0 0 anyarray_in anyarray_out anyarray_recv anyarray_send - - - d x f 0 -1 0 _null_ _null_ ));
  #define ANYARRAYOID		2277
! DATA(insert OID = 2278 ( void			PGNSP PGUID  4 t p P f t \054 0 0 0 void_in void_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define VOIDOID			2278
! DATA(insert OID = 2279 ( trigger		PGNSP PGUID  4 t p P f t \054 0 0 0 trigger_in trigger_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define TRIGGEROID		2279
! DATA(insert OID = 2280 ( language_handler	PGNSP PGUID  4 t p P f t \054 0 0 0 language_handler_in language_handler_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define LANGUAGE_HANDLEROID		2280
! DATA(insert OID = 2281 ( internal		PGNSP PGUID  SIZEOF_POINTER t p P f t \054 0 0 0 internal_in internal_out - - - - - ALIGNOF_POINTER p f 0 -1 0 _null_ _null_ ));
  #define INTERNALOID		2281
! DATA(insert OID = 2282 ( opaque			PGNSP PGUID  4 t p P f t \054 0 0 0 opaque_in opaque_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define OPAQUEOID		2282
! DATA(insert OID = 2283 ( anyelement		PGNSP PGUID  4 t p P f t \054 0 0 0 anyelement_in anyelement_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define ANYELEMENTOID	2283
! DATA(insert OID = 2776 ( anynonarray	PGNSP PGUID  4 t p P f t \054 0 0 0 anynonarray_in anynonarray_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define ANYNONARRAYOID	2776
! DATA(insert OID = 3500 ( anyenum		PGNSP PGUID  4 t p P f t \054 0 0 0 anyenum_in anyenum_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define ANYENUMOID		3500
  
  
--- 612,642 ----
   * but there is now support for it in records and arrays.  Perhaps we should
   * just treat it as a regular base type?
   */
! DATA(insert OID = 2249 ( record			PGNSP PGUID -1 f p P f t \054 0 0 2287 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  #define RECORDOID		2249
! DATA(insert OID = 2287 ( _record		PGNSP PGUID -1 f p P f t \054 0 2249 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  #define RECORDARRAYOID	2287
! DATA(insert OID = 2275 ( cstring		PGNSP PGUID -2 f p P f t \054 0 0 1263 cstring_in cstring_out cstring_recv cstring_send - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  #define CSTRINGOID		2275
! DATA(insert OID = 2276 ( any			PGNSP PGUID  4 t p P f t \054 0 0 0 any_in any_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define ANYOID			2276
! DATA(insert OID = 2277 ( anyarray		PGNSP PGUID -1 f p P f t \054 0 0 0 anyarray_in anyarray_out anyarray_recv anyarray_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  #define ANYARRAYOID		2277
! DATA(insert OID = 2278 ( void			PGNSP PGUID  4 t p P f t \054 0 0 0 void_in void_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define VOIDOID			2278
! DATA(insert OID = 2279 ( trigger		PGNSP PGUID  4 t p P f t \054 0 0 0 trigger_in trigger_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define TRIGGEROID		2279
! DATA(insert OID = 2280 ( language_handler	PGNSP PGUID  4 t p P f t \054 0 0 0 language_handler_in language_handler_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define LANGUAGE_HANDLEROID		2280
! DATA(insert OID = 2281 ( internal		PGNSP PGUID  SIZEOF_POINTER t p P f t \054 0 0 0 internal_in internal_out - - - - - ALIGNOF_POINTER p f 0 -1 0 -1 f _null_ _null_ ));
  #define INTERNALOID		2281
! DATA(insert OID = 2282 ( opaque			PGNSP PGUID  4 t p P f t \054 0 0 0 opaque_in opaque_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define OPAQUEOID		2282
! DATA(insert OID = 2283 ( anyelement		PGNSP PGUID  4 t p P f t \054 0 0 0 anyelement_in anyelement_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define ANYELEMENTOID	2283
! DATA(insert OID = 2776 ( anynonarray	PGNSP PGUID  4 t p P f t \054 0 0 0 anynonarray_in anynonarray_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define ANYNONARRAYOID	2776
! DATA(insert OID = 3500 ( anyenum		PGNSP PGUID  4 t p P f t \054 0 0 0 anyenum_in anyenum_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define ANYENUMOID		3500
  
  
*** a/src/include/catalog/pg_type_fn.h
--- b/src/include/catalog/pg_type_fn.h
***************
*** 50,56 **** extern Oid TypeCreate(Oid newTypeOid,
  		   char storage,
  		   int32 typeMod,
  		   int32 typNDims,
! 		   bool typeNotNull);
  
  extern void GenerateTypeDependencies(Oid typeNamespace,
  						 Oid typeObjectId,
--- 50,58 ----
  		   char storage,
  		   int32 typeMod,
  		   int32 typNDims,
!     	   bool typeNotNull,
! 		   int32 typeNLabels,
! 		   bool typeSorted);
  
  extern void GenerateTypeDependencies(Oid typeNamespace,
  						 Oid typeObjectId,
*** a/src/include/commands/typecmds.h
--- b/src/include/commands/typecmds.h
***************
*** 24,29 **** extern void RemoveTypes(DropStmt *drop);
--- 24,30 ----
  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);
  
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
***************
*** 348,354 **** typedef enum NodeTag
  	T_DropUserMappingStmt,
  	T_AlterTableSpaceOptionsStmt,
  	T_SecLabelStmt,
! 
  	/*
  	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
  	 */
--- 348,354 ----
  	T_DropUserMappingStmt,
  	T_AlterTableSpaceOptionsStmt,
  	T_SecLabelStmt,
! 	T_AlterEnumStmt,
  	/*
  	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
  	 */
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 2192,2197 **** typedef struct CreateEnumStmt
--- 2192,2211 ----
  
  
  /* ----------------------
+  *		Alter Type Statement, enum types
+  * ----------------------
+  */
+ typedef struct AlterEnumStmt
+ {
+ 	NodeTag		type;
+ 	List	   *typeName;		/* qualified name (list of Value strings) */
+ 	char	   *newVal;			/* new enum value */
+ 	char	   *newValNeighbour;/* neighbouring enum value */
+ 	bool	    newValIsAfter;	/* new enum value is after neighbour? */
+ } AlterEnumStmt;
+ 
+ 
+ /* ----------------------
   *		Create View Statement
   * ----------------------
   */
*** a/src/test/regress/expected/enum.out
--- b/src/test/regress/expected/enum.out
***************
*** 25,30 **** ERROR:  invalid input value for enum rainbow: "mauve"
--- 25,108 ----
  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 typnlabels, typsorted
+ FROM pg_type
+ WHERE oid = 'planets'::regtype;
+  typnlabels | typsorted 
+ ------------+-----------
+           4 | t
+ (1 row)
+ 
+ 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 typnlabels, typsorted
+ FROM pg_type
+ WHERE oid = 'planets'::regtype;
+  typnlabels | typsorted 
+ ------------+-----------
+           8 | f
+ (1 row)
+ 
+ SELECT enumlabel, enumsortorder 
+ FROM pg_enum 
+ WHERE enumtypid = 'planets'::regtype
+ ORDER by 2;
+  enumlabel | enumsortorder 
+ -----------+---------------
+  mercury   |             1
+  venus     |             2
+  earth     |             3
+  mars      |             4
+  jupiter   |             5
+  saturn    |             6
+  uranus    |             7
+  neptune   |             8
+ (8 rows)
+ 
+ select 'mars'::planets > 'mercury' as using_sortorder;
+  using_sortorder 
+ -----------------
+  t
+ (1 row)
+ 
+ -- 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 label.
+ DROP TYPE planets;
+ --
  -- Basic table creation, row selection
  --
  CREATE TABLE enumtest (col rainbow);
***************
*** 403,409 **** 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 
! -----------+-----------
  (0 rows)
  
--- 481,487 ----
  
  SELECT * FROM pg_enum WHERE NOT EXISTS
    (SELECT 1 FROM pg_type WHERE pg_type.oid = enumtypid);
!  enumtypid | enumlabel | enumsortorder 
! -----------+-----------+---------------
  (0 rows)
  
*** a/src/test/regress/sql/enum.sql
--- b/src/test/regress/sql/enum.sql
***************
*** 16,21 **** SELECT 'red'::rainbow;
--- 16,69 ----
  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 typnlabels, typsorted
+ FROM pg_type
+ WHERE oid = 'planets'::regtype;
+ 
+ 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 typnlabels, typsorted
+ FROM pg_type
+ WHERE oid = 'planets'::regtype;
+ 
+ SELECT enumlabel, enumsortorder 
+ FROM pg_enum 
+ WHERE enumtypid = 'planets'::regtype
+ ORDER by 2;
+ 
+ select 'mars'::planets > 'mercury' as using_sortorder;
+ 
+ -- errors for adding labels 
+ ALTER TYPE planets ADD 
+ 	  'plutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutopluto';
+ 
+ ALTER TYPE planets ADD 'pluto' AFTER 'zeus';
+ 
+ DROP TYPE planets;
+ --
  -- Basic table creation, row selection
  --
  CREATE TABLE enumtest (col rainbow);
#36Robert Haas
robertmhaas@gmail.com
In reply to: Andrew Dunstan (#35)
Re: WIP: extensible enums

On Wed, Oct 13, 2010 at 7:33 AM, Andrew Dunstan <andrew@dunslane.net> wrote:

Sorry, got distracted. Here's a new patch that fixes the above and also
contains some documentation.

Someone want to review this and (hopefully) mark it Ready for
Committer? I see that Brendan Jurd is the reviewer of record in the
CF app, but it seems Dean Rasheed is the person who has actually
reviewed it recently. Either way...

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

#37Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Robert Haas (#36)
Re: WIP: extensible enums

On 13 October 2010 23:17, Robert Haas <robertmhaas@gmail.com> wrote:

On Wed, Oct 13, 2010 at 7:33 AM, Andrew Dunstan <andrew@dunslane.net> wrote:

Sorry, got distracted. Here's a new patch that fixes the above and also
contains some documentation.

Someone want to review this and (hopefully) mark it Ready for
Committer?  I see that Brendan Jurd is the reviewer of record in the
CF app, but it seems Dean Rasheed is the person who has actually
reviewed it recently.  Either way...

I'm happy to take another look at it, but I'm short on time, so I
doubt that I be able to do anything before the weekend. If anyone
wants to jump in before then, feel free.

Regards,
Dean

Show quoted text

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

#38Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Dean Rasheed (#37)
1 attachment(s)
Re: WIP: extensible enums

On 14 October 2010 08:39, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

Someone want to review this and (hopefully) mark it Ready for
Committer?  I see that Brendan Jurd is the reviewer of record in the
CF app, but it seems Dean Rasheed is the person who has actually
reviewed it recently.  Either way...

I'm happy to take another look at it, but I'm short on time, so I
doubt that I be able to do anything before the weekend. If anyone
wants to jump in before then, feel free.

I started looking at this last night, but ran out of time. I'll
continue this evening / over the weekend. Here are my comments so far:

Patch applies cleanly to current git master with no offsets.
Compiles cleanly with no warnings.
Regression tests pass.

The regression tests look reasonable, but I'd like to see a test of
\dT+. Also it could be made to exercise the comparison function more
if the test query did an ORDER BY CAST(enumlabel as planets).

The docs for ALTER TYPE have been updated. I found a few minor typos,
and also a couple of other sections of the manual that needed
updating.

Attached is an updated version with these changes. Andrew, let me know
if you're happy with these tweaks and I'll continue reviewing over the
weekend.

Regards,
Dean

Attachments:

venum5.patchtext/x-patch; charset=US-ASCII; name=venum5.patchDownload
diff --git a/contrib/pg_upgrade/function.c b/contrib/pg_upgrade/function.c
index c70f23f..7dc1656 100644
*** a/contrib/pg_upgrade/function.c
--- b/contrib/pg_upgrade/function.c
*************** install_support_functions(migratorContex
*** 61,85 ****
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 				"		binary_upgrade.set_next_heap_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_toast_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_index_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			 "		binary_upgrade.add_pg_enum_label(OID, OID, NAME) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
--- 61,85 ----
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 					 "		binary_upgrade.set_next_pg_enum_oid(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 				"		binary_upgrade.set_next_heap_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_toast_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_index_relfilenode(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 c956be1..24f5e59 100644
*** a/contrib/pg_upgrade_support/pg_upgrade_support.c
--- b/contrib/pg_upgrade_support/pg_upgrade_support.c
*************** PG_MODULE_MAGIC;
*** 30,35 ****
--- 30,36 ----
  extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
  extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_array_oid;
  extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_toast_oid;
+ extern PGDLLIMPORT Oid binary_upgrade_next_pg_enum_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_ne
*** 37,54 ****
  Datum		set_next_pg_type_oid(PG_FUNCTION_ARGS);
  Datum		set_next_pg_type_array_oid(PG_FUNCTION_ARGS);
  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);
  
  PG_FUNCTION_INFO_V1(set_next_pg_type_oid);
  PG_FUNCTION_INFO_V1(set_next_pg_type_array_oid);
  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);
  
  Datum
  set_next_pg_type_oid(PG_FUNCTION_ARGS)
--- 38,55 ----
  Datum		set_next_pg_type_oid(PG_FUNCTION_ARGS);
  Datum		set_next_pg_type_array_oid(PG_FUNCTION_ARGS);
  Datum		set_next_pg_type_toast_oid(PG_FUNCTION_ARGS);
+ Datum       set_next_pg_enum_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);
  
  PG_FUNCTION_INFO_V1(set_next_pg_type_oid);
  PG_FUNCTION_INFO_V1(set_next_pg_type_array_oid);
  PG_FUNCTION_INFO_V1(set_next_pg_type_toast_oid);
+ PG_FUNCTION_INFO_V1(set_next_pg_enum_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);
  
  Datum
  set_next_pg_type_oid(PG_FUNCTION_ARGS)
*************** set_next_pg_type_toast_oid(PG_FUNCTION_A
*** 81,86 ****
--- 82,97 ----
  }
  
  Datum
+ set_next_pg_enum_oid(PG_FUNCTION_ARGS)
+ {
+ 	Oid			enumoid = PG_GETARG_OID(0);
+ 
+ 	binary_upgrade_next_pg_enum_oid = enumoid;
+ 
+ 	PG_RETURN_VOID();
+ }
+ 
+ Datum
  set_next_heap_relfilenode(PG_FUNCTION_ARGS)
  {
  	Oid			relfilenode = PG_GETARG_OID(0);
*************** set_next_index_relfilenode(PG_FUNCTION_A
*** 110,124 ****
  	PG_RETURN_VOID();
  }
  
- Datum
- add_pg_enum_label(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);
- 
- 	PG_RETURN_VOID();
- }
--- 121,123 ----
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index c37b995..0db463e 100644
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 2626,2634 ****
     matching enum types to their associated values and labels. 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.
    </para>
  
    <table>
--- 2626,2633 ----
     matching enum types to their associated values and labels. The
     internal representation of a given enum value is actually the OID
     of its associated row in <structname>pg_enum</structname>.  The
!    enum values for a particular enum type are ordered using their
!    <structfield>enumsortorder</> values.
    </para>
  
    <table>
***************
*** 2658,2663 ****
--- 2657,2670 ----
        <entry></entry>
        <entry>The textual label for this enum value</entry>
       </row>
+ 
+      <row>
+       <entry><structfield>enumsortorder</structfield></entry>
+       <entry><type>nameinteger</type></entry>
+       <entry></entry>
+       <entry>The position of this enum value within the list of values for the
+       enum type</entry>
+      </row>
      </tbody>
     </tgroup>
    </table>
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 02eaedf..78c2dac 100644
*** a/doc/src/sgml/datatype.sgml
--- b/doc/src/sgml/datatype.sgml
*************** SELECT * FROM person WHERE current_mood
*** 2898,2904 ****
  
       <para>
        The ordering of the values in an enum type is the
!       order in which the values were listed when the type was created.
        All standard comparison operators and related
        aggregate functions are supported for enums.  For example:
   
--- 2898,2906 ----
  
       <para>
        The ordering of the values in an enum type is the
!       order in which the values were listed when the type was created,
!       but additional values may be inserted at any position within this
!       list using <command>ALTER TYPE</>.
        All standard comparison operators and related
        aggregate functions are supported for enums.  For example:
   
*************** SELECT * FROM person WHERE current_mood
*** 2912,2923 ****
   Curly | ok
  (2 rows)
  
  SELECT * FROM person WHERE current_mood > 'sad' ORDER BY current_mood;
   name  | current_mood 
  -------+--------------
   Curly | ok
   Moe   | happy
! (2 rows)
  
  SELECT name 
  FROM person
--- 2914,2928 ----
   Curly | ok
  (2 rows)
  
+ ALTER TYPE mood ADD 'content' BEFORE 'happy';
+ INSERT INTO person VALUES ('Sue', 'content');
  SELECT * FROM person WHERE current_mood > 'sad' ORDER BY current_mood;
   name  | current_mood 
  -------+--------------
   Curly | ok
+  Sue   | content
   Moe   | happy
! (3 rows)
  
  SELECT name 
  FROM person
diff --git a/doc/src/sgml/ref/alter_type.sgml b/doc/src/sgml/ref/alter_type.sgml
index 315922e..b05b000 100644
*** a/doc/src/sgml/ref/alter_type.sgml
--- b/doc/src/sgml/ref/alter_type.sgml
*************** ALTER TYPE <replaceable class="PARAMETER
*** 28,33 ****
--- 28,34 ----
  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>
  
*************** ALTER TYPE <replaceable class="PARAMETER
*** 103,108 ****
--- 104,131 ----
       </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 sort order is not set using <literal>BEFORE</literal> or 
+ 	  <literal>AFTER</literal>, then the new item is placed at the end of
+ 	  the list of values.
+      </para>
+ 	 <note>
+ 	  <para>
+ 	   Adding a new enum value will in some cases lower the comparison and 
+ 	   sorting performance of the enum type. This will only usually occur if  
+ 	   <literal>BEFORE</literal> or <literal>AFTER</literal> are used to set 
+ 	   the new value's sort order position somewhere other than at the end 
+ 	   of the list. Optimal performance will be restored if the database is 
+ 	   dumped and reloaded.
+ 	  </para>
+ 	 </note>
+     </listitem>
+    </varlistentry>
    </variablelist>
    </para>
  
*************** ALTER TYPE <replaceable class="PARAMETER
*** 196,201 ****
--- 219,244 ----
        </listitem>
       </varlistentry>
  
+      <varlistentry>
+       <term><replaceable class="PARAMETER">new_enum_value</replaceable></term>
+       <listitem>
+        <para>
+         The new value to be added to the 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 neighbor of the new value to be added to the enum type's list of
+         values. Like all enum literals it needs to be quoted.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
      </variablelist>
     </para>
    </refsect1>
*************** ALTER TYPE email SET SCHEMA customers;
*** 232,237 ****
--- 275,287 ----
  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>
  
   <refsect1>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index dcc53e1..f42aecb 100644
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
*************** AddNewRelationType(const char *typeName,
*** 855,861 ****
  				   'x',			/* fully TOASTable */
  				   -1,			/* typmod */
  				   0,			/* array dimensions for typBaseType */
! 				   false);		/* Type NOT NULL */
  }
  
  /* --------------------------------
--- 855,863 ----
  				   'x',			/* fully TOASTable */
  				   -1,			/* typmod */
  				   0,			/* array dimensions for typBaseType */
! 				   false,		/* Type NOT NULL */
! 				   -1,          /* no enum labels */
! 				   false);      /* type is not an enum, so not sorted */
  }
  
  /* --------------------------------
*************** heap_create_with_catalog(const char *rel
*** 1108,1114 ****
  				   'x',			/* fully TOASTable */
  				   -1,			/* typmod */
  				   0,			/* array dimensions for typBaseType */
! 				   false);		/* Type NOT NULL */
  
  		pfree(relarrayname);
  	}
--- 1110,1118 ----
  				   'x',			/* fully TOASTable */
  				   -1,			/* typmod */
  				   0,			/* array dimensions for typBaseType */
! 				   false,		/* Type NOT NULL */
! 				   -1,          /* no enum labels */
! 				   false);      /* type is not an enum, so not sorted */
  
  		pfree(relarrayname);
  	}
diff --git a/src/backend/catalog/pg_enum.c b/src/backend/catalog/pg_enum.c
index d544c1f..65f9c71 100644
*** a/src/backend/catalog/pg_enum.c
--- b/src/backend/catalog/pg_enum.c
***************
*** 18,29 ****
--- 18,219 ----
  #include "catalog/catalog.h"
  #include "catalog/indexing.h"
  #include "catalog/pg_enum.h"
+ #include "catalog/pg_type.h"
+ #include "utils/lsyscache.h"
+ #include "utils/syscache.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
  #include "utils/rel.h"
  #include "utils/tqual.h"
  
  static int	oid_cmp(const void *p1, const void *p2);
+ static int	sort_order_cmp(const void *p1, const void *p2);
+ 
+ Oid      binary_upgrade_next_pg_enum_oid = InvalidOid;
+ 
+ /*
+  * 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.
+  *
+  * Returns true iff the labels are sorted by oid after the addition.
+  */
+ 
+ bool
+ AddEnumLabel(Oid enumTypeOid, 
+ 			 char *newVal, 
+ 			 char *neighbour, 
+ 			 bool newValIsAfter,
+ 			 int  nelems,
+ 			 bool elems_are_sorted)
+ {
+ 	Oid        newOid;
+ 	Relation   pg_enum;
+ 	TupleDesc	tupDesc;
+ 	Datum		values[Natts_pg_enum];
+ 	bool		nulls[Natts_pg_enum];
+ 	NameData	enumlabel;
+ 	HeapTuple   enum_tup;
+ 	bool        result = elems_are_sorted;
+ 	int         newelemorder;
+ 
+ 	/* 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)));
+ 
+ 	/* get a new OID for the label */
+ 	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
+ 
+ 	if (OidIsValid(binary_upgrade_next_pg_enum_oid))
+ 	{
+ 		if (neighbour != NULL)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("BEFORE/AFTER incompatible with binary upgrade.")));
+ 
+ 		newOid = binary_upgrade_next_pg_enum_oid;
+ 		binary_upgrade_next_pg_enum_oid = InvalidOid;
+ 	}
+ 	else
+ 	{
+ 		/* non upgrade case */
+ 		newOid = GetNewOid(pg_enum);
+ 	}
+ 
+ 	if (neighbour == NULL)
+ 	{
+ 		/* 
+ 		 * Put the new label at the end of the list.
+ 		 * No change to existing tuples is required.
+ 		 */
+ 		newelemorder = nelems + 1;
+ 		/* are the elements still sorted? */
+ 		if (elems_are_sorted)
+ 		{	
+ 			CatCList   *list;
+ 			int			i;
+ 			bool        still_sorted = true;
+ 
+ 			list = SearchSysCacheList1(ENUMTYPOIDNAME, 
+ 									   ObjectIdGetDatum(enumTypeOid));
+ 			for (i = 0; i < nelems; i++)
+ 			{
+ 				HeapTuple tup = &(list->members[i]->tuple);
+ 				Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
+ 
+ 				if (en->enumsortorder == nelems)
+ 				{
+ 					if (HeapTupleGetOid(tup) > newOid)
+ 						still_sorted = false;
+ 					break;
+ 				}
+ 			}
+ 			ReleaseCatCacheList(list);
+ 			if (! still_sorted)
+ 				result = false;
+ 		}
+ 	}
+ 	else 
+ 	{
+ 		/* BEFORE or AFTER specified */
+ 		CatCList   *list;
+ 		int			i;
+ 		HeapTuple    *existing;
+ 		HeapTuple     nbr = NULL;
+ 		Form_pg_enum nbr_en;
+ 
+ 		/* get a list of the existing elements and sort them by enumsortorder */
+ 		list = SearchSysCacheList1(ENUMTYPOIDNAME, 
+ 								   ObjectIdGetDatum(enumTypeOid));
+ 		existing = palloc(nelems * sizeof(HeapTuple));
+ 
+ 		for (i = 0; i < nelems; i++)
+ 			existing[i] = &(list->members[i]->tuple);
+ 
+ 		qsort(existing, nelems, sizeof(HeapTuple), sort_order_cmp);
+ 		
+ 		/* locate the neighbour element */
+ 		for (i = 0; i < nelems; i++)
+ 		{
+ 			Form_pg_enum exists_en;
+ 			exists_en = (Form_pg_enum) GETSTRUCT(existing[i]);
+ 			if (strcmp(NameStr(exists_en->enumlabel),neighbour) == 0)
+ 				nbr = existing[i];
+ 
+ 		}
+ 
+ 		if (nbr == NULL)
+ 		{
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("\"%s\" is not an existing label.", neighbour)));
+ 		}
+ 
+ 		nbr_en = (Form_pg_enum) GETSTRUCT(nbr);
+ 
+ 		/* 
+ 		 * If BEFORE was specified, the new label goes in the neighbour's
+ 		 * position. Otherwise, it goes in the position after that.
+ 		 */
+ 		newelemorder = nbr_en->enumsortorder;
+ 		if (newValIsAfter)
+ 			newelemorder++;
+ 
+ 		/* 
+ 		 * Add 1 to the sortorder of all the labels after where the
+ 		 * new label goes. Do it from the end back so we don't get
+ 		 * uniqueness violations.
+ 		 */
+ 		for (i = nelems - 1; i>= 0; i--)
+ 		{
+ 			HeapTuple newtup;
+ 			Form_pg_enum exst_en = (Form_pg_enum) GETSTRUCT(existing[i]);
+ 			if (exst_en->enumsortorder < newelemorder)
+ 				break;
+ 			
+ 			newtup = heap_copytuple(existing[i]);
+ 			exst_en = (Form_pg_enum) GETSTRUCT(newtup);
+ 			exst_en->enumsortorder ++;
+ 
+ 			simple_heap_update(pg_enum, &newtup->t_self, newtup);
+ 
+ 			CatalogUpdateIndexes(pg_enum, newtup);
+ 
+ 		}
+ 
+ 		ReleaseCatCacheList(list);
+ 
+ 		/* are the labels sorted by OID? */
+ 		if (result && newelemorder > 1)
+ 			result = newOid > HeapTupleGetOid(existing[newelemorder-2]);
+ 		if (result && newelemorder < nelems + 1)
+ 			result = newOid < HeapTupleGetOid(existing[newelemorder-1]);
+  
+ 	}
+ 
+ 	/* set up the new entry */
+ 	tupDesc = pg_enum->rd_att;
+ 	memset(nulls, false, sizeof(nulls));
+ 	values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
+ 	namestrcpy(&enumlabel, newVal);
+ 	values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
+ 	values[Anum_pg_enum_enumsortorder -1] =	Int32GetDatum(newelemorder);
+ 	enum_tup = heap_form_tuple(tupDesc, 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);
+ 
+ 	return result;
+ 
+ }
  
  
  /*
*************** static int	oid_cmp(const void *p1, const
*** 33,40 ****
   * vals is a list of Value strings.
   */
  void
! EnumValuesCreate(Oid enumTypeOid, List *vals,
! 				 Oid binary_upgrade_next_pg_enum_oid)
  {
  	Relation	pg_enum;
  	TupleDesc	tupDesc;
--- 223,229 ----
   * vals is a list of Value strings.
   */
  void
! EnumValuesCreate(Oid enumTypeOid, List *vals)
  {
  	Relation	pg_enum;
  	TupleDesc	tupDesc;
*************** EnumValuesCreate(Oid enumTypeOid, List *
*** 50,58 ****
  	num_elems = list_length(vals);
  
  	/*
! 	 * XXX 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?
  	 */
  
  	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
--- 239,247 ----
  	num_elems = list_length(vals);
  
  	/*
! 	 * 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.
! 	 * It is probably not worth trying harder.
  	 */
  
  	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
*************** EnumValuesCreate(Oid enumTypeOid, List *
*** 62,96 ****
  	 * Allocate oids
  	 */
  	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
  	{
  		/*
! 		 * 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.
  		 */
! 		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);
  	}
  
  	/* and make the entries */
  	memset(nulls, false, sizeof(nulls));
--- 251,274 ----
  	 * Allocate oids
  	 */
  	oids = (Oid *) palloc(num_elems * sizeof(Oid));
! 
! 	/*
! 	 * 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.
! 	 */
! 	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);
  
  	/* and make the entries */
  	memset(nulls, false, sizeof(nulls));
*************** EnumValuesCreate(Oid enumTypeOid, List *
*** 114,119 ****
--- 292,298 ----
  		values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
  		namestrcpy(&enumlabel, lab);
  		values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
+ 		values[Anum_pg_enum_enumsortorder -1] = Int32GetDatum(elemno+1);
  
  		tup = heap_form_tuple(tupDesc, values, nulls);
  		HeapTupleSetOid(tup, oids[elemno]);
*************** EnumValuesDelete(Oid enumTypeOid)
*** 164,170 ****
  }
  
  
! /* qsort comparison function */
  static int
  oid_cmp(const void *p1, const void *p2)
  {
--- 343,349 ----
  }
  
  
! /* qsort comparison for oids */
  static int
  oid_cmp(const void *p1, const void *p2)
  {
*************** oid_cmp(const void *p1, const void *p2)
*** 177,179 ****
--- 356,371 ----
  		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);
+ 
+ 	return en1->enumsortorder - en2->enumsortorder;
+ }
+ 
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index d7fccdf..56e0dc1 100644
*** a/src/backend/catalog/pg_type.c
--- b/src/backend/catalog/pg_type.c
*************** TypeShellMake(const char *typeName, Oid
*** 112,117 ****
--- 112,119 ----
  	values[i++] = ObjectIdGetDatum(InvalidOid); /* typbasetype */
  	values[i++] = Int32GetDatum(-1);	/* typtypmod */
  	values[i++] = Int32GetDatum(0);		/* typndims */
+ 	values[i++] = Int32GetDatum(-1);	/* typnlabels */
+ 	values[i++] = BoolGetDatum(false);	/* typsorted */
  	nulls[i++] = true;			/* typdefaultbin */
  	nulls[i++] = true;			/* typdefault */
  
*************** TypeCreate(Oid newTypeOid,
*** 204,210 ****
  		   char storage,
  		   int32 typeMod,
  		   int32 typNDims,		/* Array dimensions for baseType */
! 		   bool typeNotNull)
  {
  	Relation	pg_type_desc;
  	Oid			typeObjectId;
--- 206,214 ----
  		   char storage,
  		   int32 typeMod,
  		   int32 typNDims,		/* Array dimensions for baseType */
! 		   bool typeNotNull,
! 		   int32 typeNLabels,
! 		   bool typeSorted)
  {
  	Relation	pg_type_desc;
  	Oid			typeObjectId;
*************** TypeCreate(Oid newTypeOid,
*** 342,347 ****
--- 346,353 ----
  	values[i++] = ObjectIdGetDatum(baseType);	/* typbasetype */
  	values[i++] = Int32GetDatum(typeMod);		/* typtypmod */
  	values[i++] = Int32GetDatum(typNDims);		/* typndims */
+ 	values[i++] = Int32GetDatum(typeNLabels);	/* typnlabels */
+ 	values[i++] = BoolGetDatum(typeSorted);	    /* typsorted */
  
  	/*
  	 * initialize the default binary value for this type.  Check for nulls of
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 25503bd..01e19cf 100644
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
*************** static Oid	findTypeTypmodoutFunction(Lis
*** 85,96 ****
  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 char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
  					Oid baseTypeOid,
  					int typMod, Constraint *constr,
  					char *domainName);
- 
- 
  /*
   * DefineType
   *		Registers a new base type.
--- 85,95 ----
  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 checkEnumOwner(HeapTuple tup, TypeName *typename);
  static char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
  					Oid baseTypeOid,
  					int typMod, Constraint *constr,
  					char *domainName);
  /*
   * DefineType
   *		Registers a new base type.
*************** DefineType(List *names, List *parameters
*** 562,569 ****
  				   storage,		/* TOAST strategy */
  				   -1,			/* typMod (Domains only) */
  				   0,			/* Array Dimensions of typbasetype */
! 				   false);		/* Type NOT NULL */
! 
  	/*
  	 * Create the array type that goes with it.
  	 */
--- 561,569 ----
  				   storage,		/* TOAST strategy */
  				   -1,			/* typMod (Domains only) */
  				   0,			/* Array Dimensions of typbasetype */
! 				   false,		/* Type NOT NULL */
! 				   -1,          /* no enum labels */
! 				   false);        /* type is not an enum, so not sorted */
  	/*
  	 * Create the array type that goes with it.
  	 */
*************** DefineType(List *names, List *parameters
*** 601,607 ****
  			   'x',				/* ARRAY is always toastable */
  			   -1,				/* typMod (Domains only) */
  			   0,				/* Array dimensions of typbasetype */
! 			   false);			/* Type NOT NULL */
  
  	pfree(array_type);
  }
--- 601,609 ----
  			   'x',				/* ARRAY is always toastable */
  			   -1,				/* typMod (Domains only) */
  			   0,				/* Array dimensions of typbasetype */
! 			   false,			/* Type NOT NULL */
! 			   -1,              /* no enum labels */
! 			   false);          /* type is not an enum, so not sorted */
  
  	pfree(array_type);
  }
*************** DefineDomain(CreateDomainStmt *stmt)
*** 1044,1050 ****
  				   storage,		/* TOAST strategy */
  				   basetypeMod, /* typeMod value */
  				   typNDims,	/* Array dimensions for base type */
! 				   typNotNull); /* Type NOT NULL */
  
  	/*
  	 * Process constraints which refer to the domain ID returned by TypeCreate
--- 1046,1054 ----
  				   storage,		/* TOAST strategy */
  				   basetypeMod, /* typeMod value */
  				   typNDims,	/* Array dimensions for base type */
! 				   typNotNull,  /* Type NOT NULL */
! 				   -1,          /* no enum labels */
! 				   false);      /* type is not an enum, so not sorted */
  
  	/*
  	 * Process constraints which refer to the domain ID returned by TypeCreate
*************** DefineEnum(CreateEnumStmt *stmt)
*** 1094,1099 ****
--- 1098,1106 ----
  	AclResult	aclresult;
  	Oid			old_type_oid;
  	Oid			enumArrayOid;
+ 	int         num_labels;
+ 
+ 	num_labels = list_length(stmt->vals);
  
  	/* Convert list of names to a name and namespace */
  	enumNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
*************** DefineEnum(CreateEnumStmt *stmt)
*** 1153,1162 ****
  				   'p',			/* TOAST strategy always plain */
  				   -1,			/* typMod (Domains only) */
  				   0,			/* Array dimensions of typbasetype */
! 				   false);		/* Type NOT NULL */
  
  	/* Enter the enum's values into pg_enum */
! 	EnumValuesCreate(enumTypeOid, stmt->vals, InvalidOid);
  
  	/*
  	 * Create the array type that goes with it.
--- 1160,1171 ----
  				   'p',			/* TOAST strategy always plain */
  				   -1,			/* typMod (Domains only) */
  				   0,			/* Array dimensions of typbasetype */
! 				   false,		/* Type NOT NULL */
! 				   num_labels,  /* count enum labels */
! 				   true);       /* enums always start sorted */
  
  	/* Enter the enum's values into pg_enum */
! 	EnumValuesCreate(enumTypeOid, stmt->vals);
  
  	/*
  	 * Create the array type that goes with it.
*************** DefineEnum(CreateEnumStmt *stmt)
*** 1192,1202 ****
  			   'x',				/* ARRAY is always toastable */
  			   -1,				/* typMod (Domains only) */
  			   0,				/* Array dimensions of typbasetype */
! 			   false);			/* Type NOT NULL */
  
  	pfree(enumArrayName);
  }
  
  
  /*
   * Find suitable I/O functions for a type.
--- 1201,1288 ----
  			   'x',				/* ARRAY is always toastable */
  			   -1,				/* typMod (Domains only) */
  			   0,				/* Array dimensions of typbasetype */
! 			   false,			/* Type NOT NULL */
! 			   -1,              /* no enum labels */
! 			   false);          /* type is not an enum, so not sorted */
  
  	pfree(enumArrayName);
  }
  
+ /*
+  * AlterEnum
+  *		Registers a new label for an existing enum.
+  */
+ void
+ AlterEnum (AlterEnumStmt *stmt)
+ {
+ 	Oid			enum_type_oid;
+ 	TypeName  *typename;
+ 	bool       sorted;
+ 	HeapTuple tup, newtup;
+ 	Relation  rel;
+ 	Form_pg_type typTup;
+ 
+ 	/* Make a TypeName so we can use standard type lookup machinery */
+ 	typename = makeTypeNameFromNameList(stmt->typeName);
+ 	enum_type_oid = typenameTypeId(NULL, typename, NULL);
+ 
+ 	/* Look up the row in the type table */
+ 	rel = heap_open(TypeRelationId, RowExclusiveLock);
+ 
+ 	tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(enum_type_oid));
+ 	if (!HeapTupleIsValid(tup))
+ 		elog(ERROR, "cache lookup failed for type %u", enum_type_oid);
+ 
+ 	/* Copy the syscache entry so we can scribble on it below */
+ 	newtup = heap_copytuple(tup);
+ 	ReleaseSysCache(tup);
+ 	tup = newtup;
+ 	typTup = (Form_pg_type) GETSTRUCT(tup);
+ 
+ 	/* Check it's an enum and check user has permission to ALTER the enum */
+ 	checkEnumOwner(tup, typename);
+ 
+ 	/* Add the new label */
+ 	sorted = AddEnumLabel (enum_type_oid, stmt->newVal, 
+ 						   stmt->newValNeighbour, stmt->newValIsAfter,
+ 						   typTup->typnlabels, typTup->typsorted);
+ 
+ 	/* Update the row in pg_type */
+ 	typTup->typnlabels += 1;
+ 	typTup->typsorted = sorted;
+ 
+ 	simple_heap_update(rel, &tup->t_self, tup);
+ 
+ 	CatalogUpdateIndexes(rel, tup);
+ 
+ 	/* Clean up */
+ 	heap_close(rel, RowExclusiveLock);
+ }
+ 
+ 
+ /*
+  * 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, TypeName *typename)
+ {
+ 	Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup);
+ 
+ 	/* Check that this is actually a domain */
+ 	if (typTup->typtype != TYPTYPE_ENUM)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 				 errmsg("\"%s\" is not an enum",
+ 						TypeNameToString(typename))));
+ 
+ 	/* 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.
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 2118a33..dcc063a 100644
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyCreateEnumStmt(CreateEnumStmt *from
*** 2873,2878 ****
--- 2873,2891 ----
  	return newnode;
  }
  
+ static AlterEnumStmt *
+ _copyAlterEnumStmt(AlterEnumStmt *from)
+ {
+ 	AlterEnumStmt *newnode = makeNode(AlterEnumStmt);
+ 
+ 	COPY_NODE_FIELD(typeName);
+ 	COPY_STRING_FIELD(newVal);
+ 	COPY_STRING_FIELD(newValNeighbour);
+ 	COPY_SCALAR_FIELD(newValIsAfter);
+ 
+ 	return newnode;
+ }
+ 
  static ViewStmt *
  _copyViewStmt(ViewStmt *from)
  {
*************** copyObject(void *from)
*** 4033,4038 ****
--- 4046,4054 ----
  		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 6bad724..2439e7f 100644
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
*************** _equalCreateEnumStmt(CreateEnumStmt *a,
*** 1390,1395 ****
--- 1390,1406 ----
  }
  
  static bool
+ _equalAlterEnumStmt(AlterEnumStmt *a, AlterEnumStmt *b)
+ {
+ 	COMPARE_NODE_FIELD(typeName);
+ 	COMPARE_STRING_FIELD(newVal);
+ 	COMPARE_STRING_FIELD(newValNeighbour);
+ 	COMPARE_SCALAR_FIELD(newValIsAfter);
+ 
+ 	return true;
+ }
+ 
+ static bool
  _equalViewStmt(ViewStmt *a, ViewStmt *b)
  {
  	COMPARE_NODE_FIELD(view);
*************** equal(void *a, void *b)
*** 2697,2702 ****
--- 2708,2716 ----
  		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 3a74fa5..90d3e4d 100644
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
*************** static RangeVar *makeRangeVarFromAnyName
*** 182,189 ****
  }
  
  %type <node>	stmt schema_stmt
! 		AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterFdwStmt
! 		AlterForeignServerStmt AlterGroupStmt
  		AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
  		AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
  		AlterRoleStmt AlterRoleSetStmt
--- 182,189 ----
  }
  
  %type <node>	stmt schema_stmt
!         AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
!         AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
  		AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
  		AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
  		AlterRoleStmt AlterRoleSetStmt
*************** stmt :
*** 652,657 ****
--- 652,658 ----
  			| AlterDatabaseSetStmt
  			| AlterDefaultPrivilegesStmt
  			| AlterDomainStmt
+ 			| AlterEnumStmt
  			| AlterFdwStmt
  			| AlterForeignServerStmt
  			| AlterFunctionStmt
*************** enum_val_list:	Sconst
*** 3862,3867 ****
--- 3863,3908 ----
  				{ $$ = 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->newValNeighbour = 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->newValNeighbour = $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->newValNeighbour = $7;
+ 				 n->newValIsAfter = true;
+ 
+ 				 $$ = (Node *) n;
+ 			 }
+ 		 ;
+ 
+ 
  
  /*****************************************************************************
   *
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 75cb354..2c8b7aa 100644
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
*************** check_xact_readonly(Node *parsetree)
*** 190,195 ****
--- 190,196 ----
  		case T_CreateTrigStmt:
  		case T_CompositeTypeStmt:
  		case T_CreateEnumStmt:
+ 		case T_AlterEnumStmt:
  		case T_ViewStmt:
  		case T_DropCastStmt:
  		case T_DropStmt:
*************** standard_ProcessUtility(Node *parsetree,
*** 860,865 ****
--- 861,870 ----
  			DefineEnum((CreateEnumStmt *) parsetree);
  			break;
  
+ 		case T_AlterEnumStmt:	/* ALTER TYPE (enum) */
+ 			AlterEnum((AlterEnumStmt *) parsetree);
+ 			break;
+ 
  		case T_ViewStmt:		/* CREATE VIEW */
  			DefineView((ViewStmt *) parsetree, queryString);
  			break;
*************** CreateCommandTag(Node *parsetree)
*** 1868,1873 ****
--- 1873,1882 ----
  			tag = "CREATE TYPE";
  			break;
  
+ 		case T_AlterEnumStmt:
+ 			tag = "ALTER TYPE";
+ 			break;
+ 
  		case T_ViewStmt:
  			tag = "CREATE VIEW";
  			break;
*************** GetCommandLogLevel(Node *parsetree)
*** 2410,2415 ****
--- 2419,2428 ----
  			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 e5747a4..f9feda5 100644
*** a/src/backend/utils/adt/enum.c
--- b/src/backend/utils/adt/enum.c
***************
*** 14,19 ****
--- 14,20 ----
  #include "postgres.h"
  
  #include "catalog/pg_enum.h"
+ #include "catalog/pg_type.h"
  #include "fmgr.h"
  #include "utils/array.h"
  #include "utils/builtins.h"
***************
*** 22,30 ****
  #include "libpq/pqformat.h"
  #include "miscadmin.h"
  
  
  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 */
--- 23,48 ----
  #include "libpq/pqformat.h"
  #include "miscadmin.h"
  
+ typedef struct 
+ {
+ 	Oid      enum_oid;
+ 	int32   sort_order;
+ } enum_sort;
+ 
+ typedef struct 
+ {
+ 	Oid       enumtypoid;
+ 	bool      oids_are_sorted;
+ 	int       sort_list_length;
+ 	int       label_count;
+ 	enum_sort sort_order_list[1];
+ } enum_sort_cache;
+ 	
+ 
  
  static ArrayType *enum_range_internal(Oid enumtypoid, Oid lower, Oid upper);
! static int	enum_sort_cmp(const void *left, const void *right);
! static int	enum_oid_cmp(const void *left, const void *right);
  
  
  /* Basic I/O support */
*************** enum_send(PG_FUNCTION_ARGS)
*** 155,167 ****
  
  /* Comparison functions and related */
  
  Datum
  enum_lt(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a < b);
  }
  
  Datum
--- 173,347 ----
  
  /* Comparison functions and related */
  
+ 
+ /* enum_ccmp is the common engine for all the visible comparison functions */
+ 
+ static inline int 
+ enum_ccmp(Oid arg1, Oid arg2, FunctionCallInfo fcinfo)
+ {
+ 
+ 	/*
+ 	 * Keep a cache of type information in fcinfo->flinfo->fn_extra.
+ 	 * If the Oids are sorted, that's all the info we need, and
+ 	 * we can just return the difference of arg1 and arg2.
+ 	 * If not, we keep a list of Oid/sortorder pairs in the cache,
+ 	 * and look up the sortorder by Oid, using a binary search,
+ 	 * and return the difference between the sortorders. 
+ 	 *
+ 	 * To keep down the cost of retail comparisons even if
+ 	 * the label set is very large, we start by just putting
+ 	 * Oids we are actually comparing into the cache. If this
+ 	 * grows beyond a handful (10 in fact) it looks like it's a
+ 	 * bulk operation, and so we just fetch the all the labels
+ 	 * and sort them. This keeps down the number of times we
+ 	 * might need to call quicksort.
+ 	 */
+ 
+ 	enum_sort_cache * mycache;
+ 	enum_sort *es1, *es2, srch;
+ 	int sort1, sort2;
+ 	bool added = false;
+ 	HeapTuple	enum_tup, type_tup;
+ 	Form_pg_enum en;
+ 	Oid typeoid;
+ 	Form_pg_type typ;
+ 
+ 	/* 
+ 	 * Fast path return for equal Oids, sorted or not. 
+ 	 * This shouldn't happen, but it's here just in case.
+ 	 */
+ 	if (arg1 == arg2)
+ 		return 0;
+ 
+ 	mycache = (enum_sort_cache *) fcinfo->flinfo->fn_extra;
+ 
+ 	/* Initialize the cache if it's not already there */
+ 	if (mycache == NULL )
+ 	{
+ 		enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg1));
+ 		en = (Form_pg_enum) GETSTRUCT(enum_tup);
+ 		typeoid = en->enumtypid;
+ 		type_tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typeoid));
+ 		typ = (Form_pg_type)  GETSTRUCT(type_tup);
+ 		if (typ->typtype != 'e')
+ 			elog(ERROR,"wrong type for oid %u",typeoid);
+         fcinfo->flinfo->fn_extra = 
+ 		  MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ 							 sizeof(enum_sort_cache) + 
+ 							 (typ->typnlabels * sizeof(enum_sort)));
+         mycache = (enum_sort_cache *) fcinfo->flinfo->fn_extra;
+ 		mycache->enumtypoid = typeoid;
+ 		mycache->sort_list_length = 1;
+ 		mycache->label_count = typ->typnlabels;
+ 		mycache->oids_are_sorted = typ->typsorted;
+ 		mycache->sort_order_list[0].enum_oid = arg1;
+ 		mycache->sort_order_list[0].sort_order = en->enumsortorder;
+ 		ReleaseSysCache(type_tup);
+ 		ReleaseSysCache(enum_tup);
+ 	}
+ 
+ 	/* Fast path return for Oids are sorted case */
+ 	if (mycache->oids_are_sorted)
+ 		return arg1 - arg2;
+ 
+ 	/* Look up the Oids in the cache */
+ 	srch.enum_oid = arg1;
+ 	es1 = bsearch(&srch,
+ 				  mycache->sort_order_list,
+ 				  mycache->sort_list_length,
+ 				  sizeof(enum_sort),
+ 				  enum_oid_cmp);
+ 	srch.enum_oid = arg2;
+ 	es2 = bsearch(&srch,
+ 				  mycache->sort_order_list,
+ 				  mycache->sort_list_length,
+ 				  sizeof(enum_sort),
+ 				  enum_oid_cmp);
+ 
+ 	/* Handle cache misses, or get the sort order from the search results */
+ 
+ 	if (es1 == NULL)
+ 	{
+ 		
+ 		enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg1));
+ 		en = (Form_pg_enum) GETSTRUCT(enum_tup);
+ 		mycache->sort_order_list[mycache->sort_list_length].enum_oid = arg1;
+ 		sort1 = en->enumsortorder;
+ 		mycache->sort_order_list[mycache->sort_list_length].sort_order = 
+ 			sort1; 
+ 		ReleaseSysCache(enum_tup);
+ 		mycache->sort_list_length ++;
+ 		added = true;
+ 	}
+ 	else
+ 	{
+ 		/* already in cache */
+ 		sort1 = es1->sort_order;
+ 	}
+ 
+ 	if (es2 == NULL)
+ 	{
+ 		
+ 		enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg2));
+ 		en = (Form_pg_enum) GETSTRUCT(enum_tup);
+ 		sort2 = en->enumsortorder;
+ 		mycache->sort_order_list[mycache->sort_list_length].enum_oid = arg2;
+ 		mycache->sort_order_list[mycache->sort_list_length].sort_order = 
+ 			sort2;
+ 		ReleaseSysCache(enum_tup);
+ 		mycache->sort_list_length ++;
+ 		added = true;
+ 	}
+ 	else
+ 	{
+ 		/* already in cache */
+ 		sort2 = es2->sort_order;
+ 	}
+ 
+ 	/* Sort the cache, and possibly complete it, for the next function call */
+ 	if (added)
+ 	{
+ 		/* 
+ 		 * If we have more than a handful, just fetch them all, so we limit
+ 		 * the number of sort operations required.
+ 		 */
+ 		if (mycache->sort_list_length > 10 && 
+ 			mycache->sort_list_length < mycache->label_count)
+ 		{
+ 			CatCList   *nlist;
+ 			int			num, i;
+ 
+ 			nlist = SearchSysCacheList1(ENUMTYPOIDNAME, 
+ 									   ObjectIdGetDatum(mycache->enumtypoid));
+ 			num = nlist->n_members;
+ 			for (i = 0; i < num; i++)
+ 			{
+ 				HeapTuple tup = &(nlist->members[i]->tuple);
+ 				Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
+ 				
+ 				mycache->sort_order_list[i].enum_oid = HeapTupleGetOid(tup);
+ 				mycache->sort_order_list[i].sort_order = en->enumsortorder; 
+ 			}
+ 			mycache->sort_list_length = mycache->label_count;
+ 
+ 			ReleaseCatCacheList(nlist);
+ 		}
+ 
+ 		qsort(mycache->sort_order_list,mycache->sort_list_length,
+ 			  sizeof(enum_sort),enum_oid_cmp);
+ 	}
+ 
+ 	/* and we're done */
+ 	return sort1 - sort2;
+ }
+ 
  Datum
  enum_lt(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) < 0);
  }
  
  Datum
*************** enum_le(PG_FUNCTION_ARGS)
*** 170,176 ****
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a <= b);
  }
  
  Datum
--- 350,356 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) <= 0);
  }
  
  Datum
*************** enum_ge(PG_FUNCTION_ARGS)
*** 197,203 ****
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a >= b);
  }
  
  Datum
--- 377,383 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) >= 0);
  }
  
  Datum
*************** enum_gt(PG_FUNCTION_ARGS)
*** 206,212 ****
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a > b);
  }
  
  Datum
--- 386,392 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) > 0);
  }
  
  Datum
*************** enum_smaller(PG_FUNCTION_ARGS)
*** 215,221 ****
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(a <= b ? a : b);
  }
  
  Datum
--- 395,401 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(enum_ccmp(a,b,fcinfo) < 0 ? a : b);
  }
  
  Datum
*************** enum_larger(PG_FUNCTION_ARGS)
*** 224,230 ****
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(a >= b ? a : b);
  }
  
  Datum
--- 404,410 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(enum_ccmp(a,b,fcinfo) > 0 ? a : b);
  }
  
  Datum
*************** enum_cmp(PG_FUNCTION_ARGS)
*** 233,242 ****
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	if (a > b)
! 		PG_RETURN_INT32(1);
! 	else if (a == b)
  		PG_RETURN_INT32(0);
  	else
  		PG_RETURN_INT32(-1);
  }
--- 413,422 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	if (a == b)
  		PG_RETURN_INT32(0);
+ 	else if (enum_ccmp(a,b,fcinfo) > 0)
+ 		PG_RETURN_INT32(1);
  	else
  		PG_RETURN_INT32(-1);
  }
*************** enum_first(PG_FUNCTION_ARGS)
*** 248,253 ****
--- 428,434 ----
  {
  	Oid			enumtypoid;
  	Oid			min = InvalidOid;
+ 	int         min_sort = -1; /* value will never in fact be used */
  	CatCList   *list;
  	int			num,
  				i;
*************** enum_first(PG_FUNCTION_ARGS)
*** 267,276 ****
  	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);
--- 448,461 ----
  	num = list->n_members;
  	for (i = 0; i < num; i++)
  	{
! 		HeapTuple tup = &(list->members[i]->tuple);
! 		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
  
! 		if (!OidIsValid(min) || en->enumsortorder < min_sort)
! 		{
! 			min = HeapTupleGetOid(tup);
! 			min_sort = en->enumsortorder;
! 		}
  	}
  
  	ReleaseCatCacheList(list);
*************** enum_last(PG_FUNCTION_ARGS)
*** 287,292 ****
--- 472,478 ----
  {
  	Oid			enumtypoid;
  	Oid			max = InvalidOid;
+ 	int         max_sort = -1; /* value will never in fact be used */
  	CatCList   *list;
  	int			num,
  				i;
*************** enum_last(PG_FUNCTION_ARGS)
*** 306,315 ****
  	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);
--- 492,505 ----
  	num = list->n_members;
  	for (i = 0; i < num; i++)
  	{
! 		HeapTuple tup = &(list->members[i]->tuple);
! 		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
  
! 		if (!OidIsValid(max) || en->enumsortorder > max_sort)
! 		{
! 			max = HeapTupleGetOid(tup);
! 			max_sort = en->enumsortorder;
! 		}
  	}
  
  	ReleaseCatCacheList(list);
*************** enum_range_internal(Oid enumtypoid, Oid
*** 382,427 ****
  				i,
  				j;
  	Datum	   *elems;
  
  	list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid));
  	total = list->n_members;
  
  	elems = (Datum *) palloc(total * sizeof(Datum));
  
- 	j = 0;
  	for (i = 0; i < total; i++)
  	{
! 		Oid			val = HeapTupleGetOid(&(list->members[i]->tuple));
  
- 		if ((!OidIsValid(lower) || lower <= val) &&
- 			(!OidIsValid(upper) || val <= upper))
- 			elems[j++] = ObjectIdGetDatum(val);
  	}
  
  	/* shouldn't need the cache anymore */
  	ReleaseCatCacheList(list);
  
! 	/* sort results into OID order */
! 	qsort(elems, j, sizeof(Datum), enum_elem_cmp);
  
  	/* note this hardwires some details about the representation of Oid */
  	result = construct_array(elems, j, 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;
  }
--- 572,647 ----
  				i,
  				j;
  	Datum	   *elems;
+ 	enum_sort  *sort_items;
+ 	bool        left_found;
  
  	list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid));
  	total = list->n_members;
  
  	elems = (Datum *) palloc(total * sizeof(Datum));
+ 	sort_items = (enum_sort *) palloc(total * sizeof(enum_sort));
  
  	for (i = 0; i < total; i++)
  	{
! 		HeapTuple tup = &(list->members[i]->tuple);
! 		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
! 
! 		sort_items[i].enum_oid = HeapTupleGetOid(tup);
! 		sort_items[i].sort_order = en->enumsortorder;
  
  	}
  
  	/* shouldn't need the cache anymore */
  	ReleaseCatCacheList(list);
  
! 	/* sort results into sort_order sequence */
! 	qsort(sort_items, total, sizeof(enum_sort), enum_sort_cmp);
! 
! 	j = 0; 
! 	left_found = !OidIsValid(lower);
! 	for (i=0; i < total; i++)
! 	{
! 		if (! left_found && lower == sort_items[i].enum_oid)
! 			left_found = true;
! 
! 		if (left_found)
! 			elems[j++] = ObjectIdGetDatum(sort_items[i].enum_oid);
! 
! 		if (OidIsValid(upper) && upper == sort_items[i].enum_oid)
! 			break;
! 	}
  
  	/* note this hardwires some details about the representation of Oid */
  	result = construct_array(elems, j, enumtypoid, sizeof(Oid), true, 'i');
  
  	pfree(elems);
+ 	pfree(sort_items);
  
  	return result;
  }
  
! /* 
!  * qsort comparison using sort order, for range routines 
!  */
  static int
! enum_sort_cmp(const void *left, const void *right)
  {
! 	enum_sort  *l = (enum_sort *) left;
! 	enum_sort  *r = (enum_sort *) right;
  
! 	return l->sort_order - r->sort_order;
  }
+ 
+ /* 
+  * qsort comparison using OID order for comparison search cache
+  */
+ static int 
+ enum_oid_cmp(const void *es1, const void *es2)
+ {
+ 	enum_sort *p1, *p2;
+ 	p1 = (enum_sort *)es1;
+ 	p2 = (enum_sort *)es2;
+ 	return p1->enum_oid - p2->enum_oid;
+ }
+ 
+ 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 6a4557b..d413fe5 100644
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
*************** dumpEnumType(Archive *fout, TypeInfo *ty
*** 6653,6669 ****
  	PQExpBuffer query = createPQExpBuffer();
  	PGresult   *res;
  	int			num,
! 				i;
  	Oid			enum_oid;
  	char	   *label;
  
  	/* Set proper schema search path so regproc references list correctly */
  	selectSourceSchema(tyinfo->dobj.namespace->dobj.name);
  
! 	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);
--- 6653,6676 ----
  	PQExpBuffer query = createPQExpBuffer();
  	PGresult   *res;
  	int			num,
! 		        i;
  	Oid			enum_oid;
  	char	   *label;
  
  	/* Set proper schema search path so regproc references list correctly */
  	selectSourceSchema(tyinfo->dobj.namespace->dobj.name);
  
!     if  (fout->remoteVersion > 90000)
! 		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);
*************** dumpEnumType(Archive *fout, TypeInfo *ty
*** 6709,6725 ****
  		{
  			enum_oid = atooid(PQgetvalue(res, i, PQfnumber(res, "oid")));
  			label = PQgetvalue(res, i, PQfnumber(res, "enumlabel"));
! 
  			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);
! 			appendStringLiteralAH(q, label, fout);
! 			appendPQExpBuffer(q, ");\n");
  		}
- 		appendPQExpBuffer(q, "\n");
  	}
  
  	ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId,
--- 6716,6734 ----
  		{
  			enum_oid = atooid(PQgetvalue(res, i, PQfnumber(res, "oid")));
  			label = PQgetvalue(res, i, PQfnumber(res, "enumlabel"));
! 			
  			if (i == 0)
  				appendPQExpBuffer(q, "\n-- For binary upgrade, must preserve pg_enum oids\n");
  			appendPQExpBuffer(q,
! 							  "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\n");
  		}
  	}
  
  	ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId,
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 57d74e1..a077948 100644
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
*************** describeTypes(const char *pattern, bool
*** 473,489 ****
  						  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"
  						  "      ),\n"
  						  "      E'\\n'\n"
  						  "  ) AS \"%s\",\n",
  						  gettext_noop("Elements"));
  
  	appendPQExpBuffer(&buf,
  				"  pg_catalog.obj_description(t.oid, 'pg_type') as \"%s\"\n",
--- 473,499 ----
  						  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");
! 
! 		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/indexing.h b/src/include/catalog/indexing.h
index b7c9849..c63724a 100644
*** a/src/include/catalog/indexing.h
--- b/src/include/catalog/indexing.h
*************** DECLARE_UNIQUE_INDEX(pg_enum_oid_index,
*** 147,152 ****
--- 147,154 ----
  #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, 3539, on pg_enum using btree(enumtypid oid_ops, enumsortorder int4_ops));
+ #define EnumTypIdSortOrderIndexId 3539
  
  /* 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_class.h b/src/include/catalog/pg_class.h
index f50cf9d..824a690 100644
*** a/src/include/catalog/pg_class.h
--- b/src/include/catalog/pg_class.h
*************** typedef FormData_pg_class *Form_pg_class
*** 132,138 ****
   */
  
  /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
! DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f f r 28 0 t f f f f f 3 _null_ _null_ ));
  DESCR("");
  DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f f r 19 0 f f f f f f 3 _null_ _null_ ));
  DESCR("");
--- 132,138 ----
   */
  
  /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
! DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f f r 30 0 t f f f f f 3 _null_ _null_ ));
  DESCR("");
  DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f f r 19 0 f f f f f f 3 _null_ _null_ ));
  DESCR("");
diff --git a/src/include/catalog/pg_enum.h b/src/include/catalog/pg_enum.h
index 28da42b..06269c6 100644
*** a/src/include/catalog/pg_enum.h
--- b/src/include/catalog/pg_enum.h
*************** CATALOG(pg_enum,3501)
*** 35,40 ****
--- 35,41 ----
  {
  	Oid			enumtypid;		/* OID of owning enum type */
  	NameData	enumlabel;		/* text representation of enum value */
+ 	int4        enumsortorder;  /* sort order for this enum label */
  } FormData_pg_enum;
  
  /* ----------------
*************** typedef FormData_pg_enum *Form_pg_enum;
*** 48,56 ****
   *		compiler constants for pg_enum
   * ----------------
   */
! #define Natts_pg_enum					2
  #define Anum_pg_enum_enumtypid			1
  #define Anum_pg_enum_enumlabel			2
  
  /* ----------------
   *		pg_enum has no initial contents
--- 49,58 ----
   *		compiler constants for pg_enum
   * ----------------
   */
! #define Natts_pg_enum					3
  #define Anum_pg_enum_enumtypid			1
  #define Anum_pg_enum_enumlabel			2
+ #define Anum_pg_enum_enumsortorder		3
  
  /* ----------------
   *		pg_enum has no initial contents
*************** typedef FormData_pg_enum *Form_pg_enum;
*** 60,67 ****
  /*
   * prototypes for functions in pg_enum.c
   */
! extern void EnumValuesCreate(Oid enumTypeOid, List *vals,
! 				 Oid binary_upgrade_next_pg_enum_oid);
  extern void EnumValuesDelete(Oid enumTypeOid);
  
  #endif   /* PG_ENUM_H */
--- 62,70 ----
  /*
   * prototypes for functions in pg_enum.c
   */
! extern void EnumValuesCreate(Oid enumTypeOid, List *vals);
  extern void EnumValuesDelete(Oid enumTypeOid);
+ extern bool AddEnumLabel(Oid enumTypeOid, char *newVal, char *neighbour, 
+ 						 bool newValIsAfter, int nelems, bool elems_are_sorted);
  
  #endif   /* PG_ENUM_H */
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index fc2c306..f79c0e3 100644
*** a/src/include/catalog/pg_type.h
--- b/src/include/catalog/pg_type.h
*************** CATALOG(pg_type,1247) BKI_BOOTSTRAP BKI_
*** 194,199 ****
--- 194,211 ----
  	 */
  	int4		typndims;
  
+     /* 
+      * typnlabels, contains a count on the number of labels an enum type has, 
+      * -1 for a non-enum type.
+      */
+     int4          typnlabels;
+ 
+     /*
+      * typsorted is true if the oids of an enum type reflect the type's sort
+      * order, false otherwise including for a non-enum type.
+      */
+     bool         typsorted;
+ 
  	/*
  	 * If typdefaultbin is not NULL, it is the nodeToString representation of
  	 * a default expression for the type.  Currently this is only used for
*************** typedef FormData_pg_type *Form_pg_type;
*** 224,230 ****
   *		compiler constants for pg_type
   * ----------------
   */
! #define Natts_pg_type					28
  #define Anum_pg_type_typname			1
  #define Anum_pg_type_typnamespace		2
  #define Anum_pg_type_typowner			3
--- 236,242 ----
   *		compiler constants for pg_type
   * ----------------
   */
! #define Natts_pg_type					30
  #define Anum_pg_type_typname			1
  #define Anum_pg_type_typnamespace		2
  #define Anum_pg_type_typowner			3
*************** typedef FormData_pg_type *Form_pg_type;
*** 251,258 ****
  #define Anum_pg_type_typbasetype		24
  #define Anum_pg_type_typtypmod			25
  #define Anum_pg_type_typndims			26
! #define Anum_pg_type_typdefaultbin		27
! #define Anum_pg_type_typdefault			28
  
  
  /* ----------------
--- 263,272 ----
  #define Anum_pg_type_typbasetype		24
  #define Anum_pg_type_typtypmod			25
  #define Anum_pg_type_typndims			26
! #define Anum_pg_type_typnlabels			27
! #define Anum_pg_type_typsorted			28
! #define Anum_pg_type_typdefaultbin		29
! #define Anum_pg_type_typdefault			30
  
  
  /* ----------------
*************** typedef FormData_pg_type *Form_pg_type;
*** 269,355 ****
   */
  
  /* OIDS 1 - 99 */
! DATA(insert OID = 16 (	bool	   PGNSP PGUID	1 t b B t t \054 0	 0 1000 boolin boolout boolrecv boolsend - - - c p f 0 -1 0 _null_ _null_ ));
  DESCR("boolean, 'true'/'false'");
  #define BOOLOID			16
  
! DATA(insert OID = 17 (	bytea	   PGNSP PGUID -1 f b U f t \054 0	0 1001 byteain byteaout bytearecv byteasend - - - i x f 0 -1 0 _null_ _null_ ));
  DESCR("variable-length string, binary values escaped");
  #define BYTEAOID		17
  
! DATA(insert OID = 18 (	char	   PGNSP PGUID	1 t b S f t \054 0	 0 1002 charin charout charrecv charsend - - - c p f 0 -1 0 _null_ _null_ ));
  DESCR("single character");
  #define CHAROID			18
  
! DATA(insert OID = 19 (	name	   PGNSP PGUID NAMEDATALEN f b S f t \054 0 18 1003 namein nameout namerecv namesend - - - c p f 0 -1 0 _null_ _null_ ));
  DESCR("63-character type for storing system identifiers");
  #define NAMEOID			19
  
! DATA(insert OID = 20 (	int8	   PGNSP PGUID	8 FLOAT8PASSBYVAL b N f t \054 0	 0 1016 int8in int8out int8recv int8send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("~18 digit integer, 8-byte storage");
  #define INT8OID			20
  
! DATA(insert OID = 21 (	int2	   PGNSP PGUID	2 t b N f t \054 0	 0 1005 int2in int2out int2recv int2send - - - s p f 0 -1 0 _null_ _null_ ));
  DESCR("-32 thousand to 32 thousand, 2-byte storage");
  #define INT2OID			21
  
! DATA(insert OID = 22 (	int2vector PGNSP PGUID -1 f b A f t \054 0	21 1006 int2vectorin int2vectorout int2vectorrecv int2vectorsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("array of int2, used in system tables");
  #define INT2VECTOROID	22
  
! DATA(insert OID = 23 (	int4	   PGNSP PGUID	4 t b N f t \054 0	 0 1007 int4in int4out int4recv int4send - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("-2 billion to 2 billion integer, 4-byte storage");
  #define INT4OID			23
  
! DATA(insert OID = 24 (	regproc    PGNSP PGUID	4 t b N f t \054 0	 0 1008 regprocin regprocout regprocrecv regprocsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered procedure");
  #define REGPROCOID		24
  
! DATA(insert OID = 25 (	text	   PGNSP PGUID -1 f b S t t \054 0	0 1009 textin textout textrecv textsend - - - i x f 0 -1 0 _null_ _null_ ));
  DESCR("variable-length string, no limit specified");
  #define TEXTOID			25
  
! DATA(insert OID = 26 (	oid		   PGNSP PGUID	4 t b N t t \054 0	 0 1028 oidin oidout oidrecv oidsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("object identifier(oid), maximum 4 billion");
  #define OIDOID			26
  
! DATA(insert OID = 27 (	tid		   PGNSP PGUID	6 f b U f t \054 0	 0 1010 tidin tidout tidrecv tidsend - - - s p f 0 -1 0 _null_ _null_ ));
  DESCR("(block, offset), physical location of tuple");
  #define TIDOID		27
  
! DATA(insert OID = 28 (	xid		   PGNSP PGUID	4 t b U f t \054 0	 0 1011 xidin xidout xidrecv xidsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("transaction id");
  #define XIDOID 28
  
! DATA(insert OID = 29 (	cid		   PGNSP PGUID	4 t b U f t \054 0	 0 1012 cidin cidout cidrecv cidsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("command identifier type, sequence in transaction id");
  #define CIDOID 29
  
! DATA(insert OID = 30 (	oidvector  PGNSP PGUID -1 f b A f t \054 0	26 1013 oidvectorin oidvectorout oidvectorrecv oidvectorsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("array of oids, used in system tables");
  #define OIDVECTOROID	30
  
  /* hand-built rowtype entries for bootstrapped catalogs */
  /* NB: OIDs assigned here must match the BKI_ROWTYPE_OID declarations */
  
! DATA(insert OID = 71 (	pg_type			PGNSP PGUID -1 f c C f t \054 1247 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 75 (	pg_attribute	PGNSP PGUID -1 f c C f t \054 1249 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 81 (	pg_proc			PGNSP PGUID -1 f c C f t \054 1255 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 83 (	pg_class		PGNSP PGUID -1 f c C f t \054 1259 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 100 - 199 */
! DATA(insert OID = 142 ( xml		   PGNSP PGUID -1 f b U f t \054 0 0 143 xml_in xml_out xml_recv xml_send - - - i x f 0 -1 0 _null_ _null_ ));
  DESCR("XML content");
  #define XMLOID 142
! DATA(insert OID = 143 ( _xml	   PGNSP PGUID -1 f b A f t \054 0 142 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  
! DATA(insert OID = 194 (	pg_node_tree	PGNSP PGUID -1 f b S f t \054 0	0 0 pg_node_tree_in pg_node_tree_out pg_node_tree_recv pg_node_tree_send - - - i x f 0 -1 0 _null_ _null_ ));
  DESCR("string representing an internal node tree");
  #define PGNODETREEOID	194
  
  /* OIDS 200 - 299 */
  
! DATA(insert OID = 210 (  smgr	   PGNSP PGUID 2 t b U f t \054 0 0 0 smgrin smgrout - - - - - s p f 0 -1 0 _null_ _null_ ));
  DESCR("storage manager");
  
  /* OIDS 300 - 399 */
--- 283,369 ----
   */
  
  /* OIDS 1 - 99 */
! DATA(insert OID = 16 (	bool	   PGNSP PGUID	1 t b B t t \054 0	 0 1000 boolin boolout boolrecv boolsend - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("boolean, 'true'/'false'");
  #define BOOLOID			16
  
! DATA(insert OID = 17 (	bytea	   PGNSP PGUID -1 f b U f t \054 0	0 1001 byteain byteaout bytearecv byteasend - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("variable-length string, binary values escaped");
  #define BYTEAOID		17
  
! DATA(insert OID = 18 (	char	   PGNSP PGUID	1 t b S f t \054 0	 0 1002 charin charout charrecv charsend - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("single character");
  #define CHAROID			18
  
! DATA(insert OID = 19 (	name	   PGNSP PGUID NAMEDATALEN f b S f t \054 0 18 1003 namein nameout namerecv namesend - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("63-character type for storing system identifiers");
  #define NAMEOID			19
  
! DATA(insert OID = 20 (	int8	   PGNSP PGUID	8 FLOAT8PASSBYVAL b N f t \054 0	 0 1016 int8in int8out int8recv int8send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("~18 digit integer, 8-byte storage");
  #define INT8OID			20
  
! DATA(insert OID = 21 (	int2	   PGNSP PGUID	2 t b N f t \054 0	 0 1005 int2in int2out int2recv int2send - - - s p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("-32 thousand to 32 thousand, 2-byte storage");
  #define INT2OID			21
  
! DATA(insert OID = 22 (	int2vector PGNSP PGUID -1 f b A f t \054 0	21 1006 int2vectorin int2vectorout int2vectorrecv int2vectorsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("array of int2, used in system tables");
  #define INT2VECTOROID	22
  
! DATA(insert OID = 23 (	int4	   PGNSP PGUID	4 t b N f t \054 0	 0 1007 int4in int4out int4recv int4send - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("-2 billion to 2 billion integer, 4-byte storage");
  #define INT4OID			23
  
! DATA(insert OID = 24 (	regproc    PGNSP PGUID	4 t b N f t \054 0	 0 1008 regprocin regprocout regprocrecv regprocsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered procedure");
  #define REGPROCOID		24
  
! DATA(insert OID = 25 (	text	   PGNSP PGUID -1 f b S t t \054 0	0 1009 textin textout textrecv textsend - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("variable-length string, no limit specified");
  #define TEXTOID			25
  
! DATA(insert OID = 26 (	oid		   PGNSP PGUID	4 t b N t t \054 0	 0 1028 oidin oidout oidrecv oidsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("object identifier(oid), maximum 4 billion");
  #define OIDOID			26
  
! DATA(insert OID = 27 (	tid		   PGNSP PGUID	6 f b U f t \054 0	 0 1010 tidin tidout tidrecv tidsend - - - s p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("(block, offset), physical location of tuple");
  #define TIDOID		27
  
! DATA(insert OID = 28 (	xid		   PGNSP PGUID	4 t b U f t \054 0	 0 1011 xidin xidout xidrecv xidsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("transaction id");
  #define XIDOID 28
  
! DATA(insert OID = 29 (	cid		   PGNSP PGUID	4 t b U f t \054 0	 0 1012 cidin cidout cidrecv cidsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("command identifier type, sequence in transaction id");
  #define CIDOID 29
  
! DATA(insert OID = 30 (	oidvector  PGNSP PGUID -1 f b A f t \054 0	26 1013 oidvectorin oidvectorout oidvectorrecv oidvectorsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("array of oids, used in system tables");
  #define OIDVECTOROID	30
  
  /* hand-built rowtype entries for bootstrapped catalogs */
  /* NB: OIDs assigned here must match the BKI_ROWTYPE_OID declarations */
  
! DATA(insert OID = 71 (	pg_type			PGNSP PGUID -1 f c C f t \054 1247 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 75 (	pg_attribute	PGNSP PGUID -1 f c C f t \054 1249 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 81 (	pg_proc			PGNSP PGUID -1 f c C f t \054 1255 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 83 (	pg_class		PGNSP PGUID -1 f c C f t \054 1259 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 100 - 199 */
! DATA(insert OID = 142 ( xml		   PGNSP PGUID -1 f b U f t \054 0 0 143 xml_in xml_out xml_recv xml_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("XML content");
  #define XMLOID 142
! DATA(insert OID = 143 ( _xml	   PGNSP PGUID -1 f b A f t \054 0 142 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  
! DATA(insert OID = 194 (	pg_node_tree	PGNSP PGUID -1 f b S f t \054 0	0 0 pg_node_tree_in pg_node_tree_out pg_node_tree_recv pg_node_tree_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("string representing an internal node tree");
  #define PGNODETREEOID	194
  
  /* OIDS 200 - 299 */
  
! DATA(insert OID = 210 (  smgr	   PGNSP PGUID 2 t b U f t \054 0 0 0 smgrin smgrout - - - - - s p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("storage manager");
  
  /* OIDS 300 - 399 */
*************** DESCR("storage manager");
*** 359,589 ****
  /* OIDS 500 - 599 */
  
  /* OIDS 600 - 699 */
! DATA(insert OID = 600 (  point	   PGNSP PGUID 16 f b G f t \054 0 701 1017 point_in point_out point_recv point_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("geometric point '(x, y)'");
  #define POINTOID		600
! DATA(insert OID = 601 (  lseg	   PGNSP PGUID 32 f b G f t \054 0 600 1018 lseg_in lseg_out lseg_recv lseg_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("geometric line segment '(pt1,pt2)'");
  #define LSEGOID			601
! DATA(insert OID = 602 (  path	   PGNSP PGUID -1 f b G f t \054 0 0 1019 path_in path_out path_recv path_send - - - d x f 0 -1 0 _null_ _null_ ));
  DESCR("geometric path '(pt1,...)'");
  #define PATHOID			602
! DATA(insert OID = 603 (  box	   PGNSP PGUID 32 f b G f t \073 0 600 1020 box_in box_out box_recv box_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("geometric box '(lower left,upper right)'");
  #define BOXOID			603
! DATA(insert OID = 604 (  polygon   PGNSP PGUID -1 f b G f t \054 0	 0 1027 poly_in poly_out poly_recv poly_send - - - d x f 0 -1 0 _null_ _null_ ));
  DESCR("geometric polygon '(pt1,...)'");
  #define POLYGONOID		604
  
! DATA(insert OID = 628 (  line	   PGNSP PGUID 32 f b G f t \054 0 701 629 line_in line_out line_recv line_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("geometric line (not implemented)");
  #define LINEOID			628
! DATA(insert OID = 629 (  _line	   PGNSP PGUID	-1 f b A f t \054 0 628 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
  DESCR("");
  
  /* OIDS 700 - 799 */
  
! DATA(insert OID = 700 (  float4    PGNSP PGUID	4 FLOAT4PASSBYVAL b N f t \054 0	 0 1021 float4in float4out float4recv float4send - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("single-precision floating point number, 4-byte storage");
  #define FLOAT4OID 700
! DATA(insert OID = 701 (  float8    PGNSP PGUID	8 FLOAT8PASSBYVAL b N t t \054 0	 0 1022 float8in float8out float8recv float8send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("double-precision floating point number, 8-byte storage");
  #define FLOAT8OID 701
! DATA(insert OID = 702 (  abstime   PGNSP PGUID	4 t b D f t \054 0	 0 1023 abstimein abstimeout abstimerecv abstimesend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("absolute, limited-range date and time (Unix system time)");
  #define ABSTIMEOID		702
! DATA(insert OID = 703 (  reltime   PGNSP PGUID	4 t b T f t \054 0	 0 1024 reltimein reltimeout reltimerecv reltimesend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("relative, limited-range time interval (Unix delta time)");
  #define RELTIMEOID		703
! DATA(insert OID = 704 (  tinterval PGNSP PGUID 12 f b T f t \054 0	 0 1025 tintervalin tintervalout tintervalrecv tintervalsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("(abstime,abstime), time interval");
  #define TINTERVALOID	704
! DATA(insert OID = 705 (  unknown   PGNSP PGUID -2 f b X f t \054 0	 0 0 unknownin unknownout unknownrecv unknownsend - - - c p f 0 -1 0 _null_ _null_ ));
  DESCR("");
  #define UNKNOWNOID		705
  
! DATA(insert OID = 718 (  circle    PGNSP PGUID	24 f b G f t \054 0 0 719 circle_in circle_out circle_recv circle_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("geometric circle '(center,radius)'");
  #define CIRCLEOID		718
! DATA(insert OID = 719 (  _circle   PGNSP PGUID	-1 f b A f t \054 0  718 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 790 (  money	   PGNSP PGUID	 8 FLOAT8PASSBYVAL b N f t \054 0 0 791 cash_in cash_out cash_recv cash_send - - - d p f 0 -1 0 _null_ _null_ ));
  DESCR("monetary amounts, $d,ddd.cc");
  #define CASHOID 790
! DATA(insert OID = 791 (  _money    PGNSP PGUID	-1 f b A f t \054 0  790 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 800 - 899 */
! DATA(insert OID = 829 ( macaddr    PGNSP PGUID	6 f b U f t \054 0 0 1040 macaddr_in macaddr_out macaddr_recv macaddr_send - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("XX:XX:XX:XX:XX:XX, MAC address");
  #define MACADDROID 829
! DATA(insert OID = 869 ( inet	   PGNSP PGUID	-1 f b I t t \054 0 0 1041 inet_in inet_out inet_recv inet_send - - - i m f 0 -1 0 _null_ _null_ ));
  DESCR("IP address/netmask, host address, netmask optional");
  #define INETOID 869
! DATA(insert OID = 650 ( cidr	   PGNSP PGUID	-1 f b I f t \054 0 0 651 cidr_in cidr_out cidr_recv cidr_send - - - i m f 0 -1 0 _null_ _null_ ));
  DESCR("network IP address/netmask, network address");
  #define CIDROID 650
  
  /* OIDS 900 - 999 */
  
  /* OIDS 1000 - 1099 */
! DATA(insert OID = 1000 (  _bool		 PGNSP PGUID -1 f b A f t \054 0	16 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1001 (  _bytea	 PGNSP PGUID -1 f b A f t \054 0	17 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1002 (  _char		 PGNSP PGUID -1 f b A f t \054 0	18 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1003 (  _name		 PGNSP PGUID -1 f b A f t \054 0	19 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1005 (  _int2		 PGNSP PGUID -1 f b A f t \054 0	21 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1006 (  _int2vector PGNSP PGUID -1 f b A f t \054 0	22 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1007 (  _int4		 PGNSP PGUID -1 f b A f t \054 0	23 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  #define INT4ARRAYOID		1007
! DATA(insert OID = 1008 (  _regproc	 PGNSP PGUID -1 f b A f t \054 0	24 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1009 (  _text		 PGNSP PGUID -1 f b A f t \054 0	25 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  #define TEXTARRAYOID		1009
! DATA(insert OID = 1028 (  _oid		 PGNSP PGUID -1 f b A f t \054 0	26 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1010 (  _tid		 PGNSP PGUID -1 f b A f t \054 0	27 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1011 (  _xid		 PGNSP PGUID -1 f b A f t \054 0	28 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1012 (  _cid		 PGNSP PGUID -1 f b A f t \054 0	29 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1013 (  _oidvector PGNSP PGUID -1 f b A f t \054 0	30 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1014 (  _bpchar	 PGNSP PGUID -1 f b A f t \054 0 1042 0 array_in array_out array_recv array_send bpchartypmodin bpchartypmodout - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1015 (  _varchar	 PGNSP PGUID -1 f b A f t \054 0 1043 0 array_in array_out array_recv array_send varchartypmodin varchartypmodout - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1016 (  _int8		 PGNSP PGUID -1 f b A f t \054 0	20 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1017 (  _point	 PGNSP PGUID -1 f b A f t \054 0 600 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1018 (  _lseg		 PGNSP PGUID -1 f b A f t \054 0 601 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1019 (  _path		 PGNSP PGUID -1 f b A f t \054 0 602 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1020 (  _box		 PGNSP PGUID -1 f b A f t \073 0 603 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1021 (  _float4	 PGNSP PGUID -1 f b A f t \054 0 700 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  #define FLOAT4ARRAYOID 1021
! DATA(insert OID = 1022 (  _float8	 PGNSP PGUID -1 f b A f t \054 0 701 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1023 (  _abstime	 PGNSP PGUID -1 f b A f t \054 0 702 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1024 (  _reltime	 PGNSP PGUID -1 f b A f t \054 0 703 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1025 (  _tinterval PGNSP PGUID -1 f b A f t \054 0 704 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1027 (  _polygon	 PGNSP PGUID -1 f b A f t \054 0 604 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1033 (  aclitem	 PGNSP PGUID 12 f b U f t \054 0 0 1034 aclitemin aclitemout - - - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("access control list");
  #define ACLITEMOID		1033
! DATA(insert OID = 1034 (  _aclitem	 PGNSP PGUID -1 f b A f t \054 0 1033 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1040 (  _macaddr	 PGNSP PGUID -1 f b A f t \054 0  829 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1041 (  _inet		 PGNSP PGUID -1 f b A f t \054 0  869 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 651  (  _cidr		 PGNSP PGUID -1 f b A f t \054 0  650 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1263 (  _cstring	 PGNSP PGUID -1 f b A f t \054 0 2275 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  #define CSTRINGARRAYOID		1263
  
! DATA(insert OID = 1042 ( bpchar		 PGNSP PGUID -1 f b S f t \054 0	0 1014 bpcharin bpcharout bpcharrecv bpcharsend bpchartypmodin bpchartypmodout - i x f 0 -1 0 _null_ _null_ ));
  DESCR("char(length), blank-padded string, fixed storage length");
  #define BPCHAROID		1042
! DATA(insert OID = 1043 ( varchar	 PGNSP PGUID -1 f b S f t \054 0	0 1015 varcharin varcharout varcharrecv varcharsend varchartypmodin varchartypmodout - i x f 0 -1 0 _null_ _null_ ));
  DESCR("varchar(length), non-blank-padded string, variable storage length");
  #define VARCHAROID		1043
  
! DATA(insert OID = 1082 ( date		 PGNSP PGUID	4 t b D f t \054 0	0 1182 date_in date_out date_recv date_send - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("date");
  #define DATEOID			1082
! DATA(insert OID = 1083 ( time		 PGNSP PGUID	8 FLOAT8PASSBYVAL b D f t \054 0	0 1183 time_in time_out time_recv time_send timetypmodin timetypmodout - d p f 0 -1 0 _null_ _null_ ));
  DESCR("time of day");
  #define TIMEOID			1083
  
  /* OIDS 1100 - 1199 */
! DATA(insert OID = 1114 ( timestamp	 PGNSP PGUID	8 FLOAT8PASSBYVAL b D f t \054 0	0 1115 timestamp_in timestamp_out timestamp_recv timestamp_send timestamptypmodin timestamptypmodout - d p f 0 -1 0 _null_ _null_ ));
  DESCR("date and time");
  #define TIMESTAMPOID	1114
! DATA(insert OID = 1115 ( _timestamp  PGNSP PGUID	-1 f b A f t \054 0 1114 0 array_in array_out array_recv array_send timestamptypmodin timestamptypmodout - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1182 ( _date		 PGNSP PGUID	-1 f b A f t \054 0 1082 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1183 ( _time		 PGNSP PGUID	-1 f b A f t \054 0 1083 0 array_in array_out array_recv array_send timetypmodin timetypmodout - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1184 ( timestamptz PGNSP PGUID	8 FLOAT8PASSBYVAL b D t t \054 0	0 1185 timestamptz_in timestamptz_out timestamptz_recv timestamptz_send timestamptztypmodin timestamptztypmodout - d p f 0 -1 0 _null_ _null_ ));
  DESCR("date and time with time zone");
  #define TIMESTAMPTZOID	1184
! DATA(insert OID = 1185 ( _timestamptz PGNSP PGUID -1 f b A f t \054 0	1184 0 array_in array_out array_recv array_send timestamptztypmodin timestamptztypmodout - d x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1186 ( interval	 PGNSP PGUID 16 f b T t t \054 0	0 1187 interval_in interval_out interval_recv interval_send intervaltypmodin intervaltypmodout - d p f 0 -1 0 _null_ _null_ ));
  DESCR("@ <number> <units>, time interval");
  #define INTERVALOID		1186
! DATA(insert OID = 1187 ( _interval	 PGNSP PGUID	-1 f b A f t \054 0 1186 0 array_in array_out array_recv array_send intervaltypmodin intervaltypmodout - d x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 1200 - 1299 */
! DATA(insert OID = 1231 (  _numeric	 PGNSP PGUID -1 f b A f t \054 0	1700 0 array_in array_out array_recv array_send numerictypmodin numerictypmodout - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1266 ( timetz		 PGNSP PGUID 12 f b D f t \054 0	0 1270 timetz_in timetz_out timetz_recv timetz_send timetztypmodin timetztypmodout - d p f 0 -1 0 _null_ _null_ ));
  DESCR("time of day with time zone");
  #define TIMETZOID		1266
! DATA(insert OID = 1270 ( _timetz	 PGNSP PGUID -1 f b A f t \054 0	1266 0 array_in array_out array_recv array_send timetztypmodin timetztypmodout - d x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 1500 - 1599 */
! DATA(insert OID = 1560 ( bit		 PGNSP PGUID -1 f b V f t \054 0	0 1561 bit_in bit_out bit_recv bit_send bittypmodin bittypmodout - i x f 0 -1 0 _null_ _null_ ));
  DESCR("fixed-length bit string");
  #define BITOID	 1560
! DATA(insert OID = 1561 ( _bit		 PGNSP PGUID -1 f b A f t \054 0	1560 0 array_in array_out array_recv array_send bittypmodin bittypmodout - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 1562 ( varbit		 PGNSP PGUID -1 f b V t t \054 0	0 1563 varbit_in varbit_out varbit_recv varbit_send varbittypmodin varbittypmodout - i x f 0 -1 0 _null_ _null_ ));
  DESCR("variable-length bit string");
  #define VARBITOID	  1562
! DATA(insert OID = 1563 ( _varbit	 PGNSP PGUID -1 f b A f t \054 0	1562 0 array_in array_out array_recv array_send varbittypmodin varbittypmodout - i x f 0 -1 0 _null_ _null_ ));
  
  /* OIDS 1600 - 1699 */
  
  /* OIDS 1700 - 1799 */
! DATA(insert OID = 1700 ( numeric	   PGNSP PGUID -1 f b N f t \054 0	0 1231 numeric_in numeric_out numeric_recv numeric_send numerictypmodin numerictypmodout - i m f 0 -1 0 _null_ _null_ ));
  DESCR("numeric(precision, decimal), arbitrary precision number");
  #define NUMERICOID		1700
  
! DATA(insert OID = 1790 ( refcursor	   PGNSP PGUID -1 f b U f t \054 0	0 2201 textin textout textrecv textsend - - - i x f 0 -1 0 _null_ _null_ ));
  DESCR("reference to cursor (portal name)");
  #define REFCURSOROID	1790
  
  /* OIDS 2200 - 2299 */
! DATA(insert OID = 2201 ( _refcursor    PGNSP PGUID -1 f b A f t \054 0 1790 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  
! DATA(insert OID = 2202 ( regprocedure  PGNSP PGUID	4 t b N f t \054 0	 0 2207 regprocedurein regprocedureout regprocedurerecv regproceduresend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered procedure (with args)");
  #define REGPROCEDUREOID 2202
  
! DATA(insert OID = 2203 ( regoper	   PGNSP PGUID	4 t b N f t \054 0	 0 2208 regoperin regoperout regoperrecv regopersend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered operator");
  #define REGOPEROID		2203
  
! DATA(insert OID = 2204 ( regoperator   PGNSP PGUID	4 t b N f t \054 0	 0 2209 regoperatorin regoperatorout regoperatorrecv regoperatorsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered operator (with args)");
  #define REGOPERATOROID	2204
  
! DATA(insert OID = 2205 ( regclass	   PGNSP PGUID	4 t b N f t \054 0	 0 2210 regclassin regclassout regclassrecv regclasssend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered class");
  #define REGCLASSOID		2205
  
! DATA(insert OID = 2206 ( regtype	   PGNSP PGUID	4 t b N f t \054 0	 0 2211 regtypein regtypeout regtyperecv regtypesend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered type");
  #define REGTYPEOID		2206
  
! DATA(insert OID = 2207 ( _regprocedure PGNSP PGUID -1 f b A f t \054 0 2202 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 2208 ( _regoper	   PGNSP PGUID -1 f b A f t \054 0 2203 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 2209 ( _regoperator  PGNSP PGUID -1 f b A f t \054 0 2204 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 2210 ( _regclass	   PGNSP PGUID -1 f b A f t \054 0 2205 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 2211 ( _regtype	   PGNSP PGUID -1 f b A f t \054 0 2206 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  #define REGTYPEARRAYOID 2211
  
  /* uuid */
! DATA(insert OID = 2950 ( uuid			PGNSP PGUID 16 f b U f t \054 0 0 2951 uuid_in uuid_out uuid_recv uuid_send - - - c p f 0 -1 0 _null_ _null_ ));
  DESCR("UUID datatype");
! DATA(insert OID = 2951 ( _uuid			PGNSP PGUID -1 f b A f t \054 0 2950 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  
  /* text search */
! DATA(insert OID = 3614 ( tsvector		PGNSP PGUID -1 f b U f t \054 0 0 3643 tsvectorin tsvectorout tsvectorrecv tsvectorsend - - ts_typanalyze i x f 0 -1 0 _null_ _null_ ));
  DESCR("text representation for text search");
  #define TSVECTOROID		3614
! DATA(insert OID = 3642 ( gtsvector		PGNSP PGUID -1 f b U f t \054 0 0 3644 gtsvectorin gtsvectorout - - - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("GiST index internal text representation for text search");
  #define GTSVECTOROID	3642
! DATA(insert OID = 3615 ( tsquery		PGNSP PGUID -1 f b U f t \054 0 0 3645 tsqueryin tsqueryout tsqueryrecv tsquerysend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("query representation for text search");
  #define TSQUERYOID		3615
! DATA(insert OID = 3734 ( regconfig		PGNSP PGUID 4 t b N f t \054 0 0 3735 regconfigin regconfigout regconfigrecv regconfigsend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered text search configuration");
  #define REGCONFIGOID	3734
! DATA(insert OID = 3769 ( regdictionary	PGNSP PGUID 4 t b N f t \054 0 0 3770 regdictionaryin regdictionaryout regdictionaryrecv regdictionarysend - - - i p f 0 -1 0 _null_ _null_ ));
  DESCR("registered text search dictionary");
  #define REGDICTIONARYOID	3769
  
! DATA(insert OID = 3643 ( _tsvector		PGNSP PGUID -1 f b A f t \054 0 3614 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 3644 ( _gtsvector		PGNSP PGUID -1 f b A f t \054 0 3642 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 3645 ( _tsquery		PGNSP PGUID -1 f b A f t \054 0 3615 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 3735 ( _regconfig		PGNSP PGUID -1 f b A f t \054 0 3734 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
! DATA(insert OID = 3770 ( _regdictionary PGNSP PGUID -1 f b A f t \054 0 3769 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ ));
  
! DATA(insert OID = 2970 ( txid_snapshot	PGNSP PGUID -1 f b U f t \054 0 0 2949 txid_snapshot_in txid_snapshot_out txid_snapshot_recv txid_snapshot_send - - - d x f 0 -1 0 _null_ _null_ ));
  DESCR("txid snapshot");
! DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
  
  /*
   * pseudo-types
--- 373,603 ----
  /* OIDS 500 - 599 */
  
  /* OIDS 600 - 699 */
! DATA(insert OID = 600 (  point	   PGNSP PGUID 16 f b G f t \054 0 701 1017 point_in point_out point_recv point_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric point '(x, y)'");
  #define POINTOID		600
! DATA(insert OID = 601 (  lseg	   PGNSP PGUID 32 f b G f t \054 0 600 1018 lseg_in lseg_out lseg_recv lseg_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric line segment '(pt1,pt2)'");
  #define LSEGOID			601
! DATA(insert OID = 602 (  path	   PGNSP PGUID -1 f b G f t \054 0 0 1019 path_in path_out path_recv path_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric path '(pt1,...)'");
  #define PATHOID			602
! DATA(insert OID = 603 (  box	   PGNSP PGUID 32 f b G f t \073 0 600 1020 box_in box_out box_recv box_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric box '(lower left,upper right)'");
  #define BOXOID			603
! DATA(insert OID = 604 (  polygon   PGNSP PGUID -1 f b G f t \054 0	 0 1027 poly_in poly_out poly_recv poly_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric polygon '(pt1,...)'");
  #define POLYGONOID		604
  
! DATA(insert OID = 628 (  line	   PGNSP PGUID 32 f b G f t \054 0 701 629 line_in line_out line_recv line_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric line (not implemented)");
  #define LINEOID			628
! DATA(insert OID = 629 (  _line	   PGNSP PGUID	-1 f b A f t \054 0 628 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("");
  
  /* OIDS 700 - 799 */
  
! DATA(insert OID = 700 (  float4    PGNSP PGUID	4 FLOAT4PASSBYVAL b N f t \054 0	 0 1021 float4in float4out float4recv float4send - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("single-precision floating point number, 4-byte storage");
  #define FLOAT4OID 700
! DATA(insert OID = 701 (  float8    PGNSP PGUID	8 FLOAT8PASSBYVAL b N t t \054 0	 0 1022 float8in float8out float8recv float8send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("double-precision floating point number, 8-byte storage");
  #define FLOAT8OID 701
! DATA(insert OID = 702 (  abstime   PGNSP PGUID	4 t b D f t \054 0	 0 1023 abstimein abstimeout abstimerecv abstimesend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("absolute, limited-range date and time (Unix system time)");
  #define ABSTIMEOID		702
! DATA(insert OID = 703 (  reltime   PGNSP PGUID	4 t b T f t \054 0	 0 1024 reltimein reltimeout reltimerecv reltimesend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("relative, limited-range time interval (Unix delta time)");
  #define RELTIMEOID		703
! DATA(insert OID = 704 (  tinterval PGNSP PGUID 12 f b T f t \054 0	 0 1025 tintervalin tintervalout tintervalrecv tintervalsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("(abstime,abstime), time interval");
  #define TINTERVALOID	704
! DATA(insert OID = 705 (  unknown   PGNSP PGUID -2 f b X f t \054 0	 0 0 unknownin unknownout unknownrecv unknownsend - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("");
  #define UNKNOWNOID		705
  
! DATA(insert OID = 718 (  circle    PGNSP PGUID	24 f b G f t \054 0 0 719 circle_in circle_out circle_recv circle_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("geometric circle '(center,radius)'");
  #define CIRCLEOID		718
! DATA(insert OID = 719 (  _circle   PGNSP PGUID	-1 f b A f t \054 0  718 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 790 (  money	   PGNSP PGUID	 8 FLOAT8PASSBYVAL b N f t \054 0 0 791 cash_in cash_out cash_recv cash_send - - - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("monetary amounts, $d,ddd.cc");
  #define CASHOID 790
! DATA(insert OID = 791 (  _money    PGNSP PGUID	-1 f b A f t \054 0  790 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 800 - 899 */
! DATA(insert OID = 829 ( macaddr    PGNSP PGUID	6 f b U f t \054 0 0 1040 macaddr_in macaddr_out macaddr_recv macaddr_send - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("XX:XX:XX:XX:XX:XX, MAC address");
  #define MACADDROID 829
! DATA(insert OID = 869 ( inet	   PGNSP PGUID	-1 f b I t t \054 0 0 1041 inet_in inet_out inet_recv inet_send - - - i m f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("IP address/netmask, host address, netmask optional");
  #define INETOID 869
! DATA(insert OID = 650 ( cidr	   PGNSP PGUID	-1 f b I f t \054 0 0 651 cidr_in cidr_out cidr_recv cidr_send - - - i m f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("network IP address/netmask, network address");
  #define CIDROID 650
  
  /* OIDS 900 - 999 */
  
  /* OIDS 1000 - 1099 */
! DATA(insert OID = 1000 (  _bool		 PGNSP PGUID -1 f b A f t \054 0	16 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1001 (  _bytea	 PGNSP PGUID -1 f b A f t \054 0	17 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1002 (  _char		 PGNSP PGUID -1 f b A f t \054 0	18 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1003 (  _name		 PGNSP PGUID -1 f b A f t \054 0	19 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1005 (  _int2		 PGNSP PGUID -1 f b A f t \054 0	21 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1006 (  _int2vector PGNSP PGUID -1 f b A f t \054 0	22 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1007 (  _int4		 PGNSP PGUID -1 f b A f t \054 0	23 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  #define INT4ARRAYOID		1007
! DATA(insert OID = 1008 (  _regproc	 PGNSP PGUID -1 f b A f t \054 0	24 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1009 (  _text		 PGNSP PGUID -1 f b A f t \054 0	25 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  #define TEXTARRAYOID		1009
! DATA(insert OID = 1028 (  _oid		 PGNSP PGUID -1 f b A f t \054 0	26 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1010 (  _tid		 PGNSP PGUID -1 f b A f t \054 0	27 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1011 (  _xid		 PGNSP PGUID -1 f b A f t \054 0	28 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1012 (  _cid		 PGNSP PGUID -1 f b A f t \054 0	29 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1013 (  _oidvector PGNSP PGUID -1 f b A f t \054 0	30 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1014 (  _bpchar	 PGNSP PGUID -1 f b A f t \054 0 1042 0 array_in array_out array_recv array_send bpchartypmodin bpchartypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1015 (  _varchar	 PGNSP PGUID -1 f b A f t \054 0 1043 0 array_in array_out array_recv array_send varchartypmodin varchartypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1016 (  _int8		 PGNSP PGUID -1 f b A f t \054 0	20 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1017 (  _point	 PGNSP PGUID -1 f b A f t \054 0 600 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1018 (  _lseg		 PGNSP PGUID -1 f b A f t \054 0 601 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1019 (  _path		 PGNSP PGUID -1 f b A f t \054 0 602 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1020 (  _box		 PGNSP PGUID -1 f b A f t \073 0 603 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1021 (  _float4	 PGNSP PGUID -1 f b A f t \054 0 700 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  #define FLOAT4ARRAYOID 1021
! DATA(insert OID = 1022 (  _float8	 PGNSP PGUID -1 f b A f t \054 0 701 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1023 (  _abstime	 PGNSP PGUID -1 f b A f t \054 0 702 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1024 (  _reltime	 PGNSP PGUID -1 f b A f t \054 0 703 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1025 (  _tinterval PGNSP PGUID -1 f b A f t \054 0 704 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1027 (  _polygon	 PGNSP PGUID -1 f b A f t \054 0 604 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1033 (  aclitem	 PGNSP PGUID 12 f b U f t \054 0 0 1034 aclitemin aclitemout - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("access control list");
  #define ACLITEMOID		1033
! DATA(insert OID = 1034 (  _aclitem	 PGNSP PGUID -1 f b A f t \054 0 1033 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1040 (  _macaddr	 PGNSP PGUID -1 f b A f t \054 0  829 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1041 (  _inet		 PGNSP PGUID -1 f b A f t \054 0  869 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 651  (  _cidr		 PGNSP PGUID -1 f b A f t \054 0  650 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1263 (  _cstring	 PGNSP PGUID -1 f b A f t \054 0 2275 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  #define CSTRINGARRAYOID		1263
  
! DATA(insert OID = 1042 ( bpchar		 PGNSP PGUID -1 f b S f t \054 0	0 1014 bpcharin bpcharout bpcharrecv bpcharsend bpchartypmodin bpchartypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("char(length), blank-padded string, fixed storage length");
  #define BPCHAROID		1042
! DATA(insert OID = 1043 ( varchar	 PGNSP PGUID -1 f b S f t \054 0	0 1015 varcharin varcharout varcharrecv varcharsend varchartypmodin varchartypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("varchar(length), non-blank-padded string, variable storage length");
  #define VARCHAROID		1043
  
! DATA(insert OID = 1082 ( date		 PGNSP PGUID	4 t b D f t \054 0	0 1182 date_in date_out date_recv date_send - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("date");
  #define DATEOID			1082
! DATA(insert OID = 1083 ( time		 PGNSP PGUID	8 FLOAT8PASSBYVAL b D f t \054 0	0 1183 time_in time_out time_recv time_send timetypmodin timetypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("time of day");
  #define TIMEOID			1083
  
  /* OIDS 1100 - 1199 */
! DATA(insert OID = 1114 ( timestamp	 PGNSP PGUID	8 FLOAT8PASSBYVAL b D f t \054 0	0 1115 timestamp_in timestamp_out timestamp_recv timestamp_send timestamptypmodin timestamptypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("date and time");
  #define TIMESTAMPOID	1114
! DATA(insert OID = 1115 ( _timestamp  PGNSP PGUID	-1 f b A f t \054 0 1114 0 array_in array_out array_recv array_send timestamptypmodin timestamptypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1182 ( _date		 PGNSP PGUID	-1 f b A f t \054 0 1082 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1183 ( _time		 PGNSP PGUID	-1 f b A f t \054 0 1083 0 array_in array_out array_recv array_send timetypmodin timetypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1184 ( timestamptz PGNSP PGUID	8 FLOAT8PASSBYVAL b D t t \054 0	0 1185 timestamptz_in timestamptz_out timestamptz_recv timestamptz_send timestamptztypmodin timestamptztypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("date and time with time zone");
  #define TIMESTAMPTZOID	1184
! DATA(insert OID = 1185 ( _timestamptz PGNSP PGUID -1 f b A f t \054 0	1184 0 array_in array_out array_recv array_send timestamptztypmodin timestamptztypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1186 ( interval	 PGNSP PGUID 16 f b T t t \054 0	0 1187 interval_in interval_out interval_recv interval_send intervaltypmodin intervaltypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("@ <number> <units>, time interval");
  #define INTERVALOID		1186
! DATA(insert OID = 1187 ( _interval	 PGNSP PGUID	-1 f b A f t \054 0 1186 0 array_in array_out array_recv array_send intervaltypmodin intervaltypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 1200 - 1299 */
! DATA(insert OID = 1231 (  _numeric	 PGNSP PGUID -1 f b A f t \054 0	1700 0 array_in array_out array_recv array_send numerictypmodin numerictypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1266 ( timetz		 PGNSP PGUID 12 f b D f t \054 0	0 1270 timetz_in timetz_out timetz_recv timetz_send timetztypmodin timetztypmodout - d p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("time of day with time zone");
  #define TIMETZOID		1266
! DATA(insert OID = 1270 ( _timetz	 PGNSP PGUID -1 f b A f t \054 0	1266 0 array_in array_out array_recv array_send timetztypmodin timetztypmodout - d x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 1500 - 1599 */
! DATA(insert OID = 1560 ( bit		 PGNSP PGUID -1 f b V f t \054 0	0 1561 bit_in bit_out bit_recv bit_send bittypmodin bittypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("fixed-length bit string");
  #define BITOID	 1560
! DATA(insert OID = 1561 ( _bit		 PGNSP PGUID -1 f b A f t \054 0	1560 0 array_in array_out array_recv array_send bittypmodin bittypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 1562 ( varbit		 PGNSP PGUID -1 f b V t t \054 0	0 1563 varbit_in varbit_out varbit_recv varbit_send varbittypmodin varbittypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("variable-length bit string");
  #define VARBITOID	  1562
! DATA(insert OID = 1563 ( _varbit	 PGNSP PGUID -1 f b A f t \054 0	1562 0 array_in array_out array_recv array_send varbittypmodin varbittypmodout - i x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* OIDS 1600 - 1699 */
  
  /* OIDS 1700 - 1799 */
! DATA(insert OID = 1700 ( numeric	   PGNSP PGUID -1 f b N f t \054 0	0 1231 numeric_in numeric_out numeric_recv numeric_send numerictypmodin numerictypmodout - i m f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("numeric(precision, decimal), arbitrary precision number");
  #define NUMERICOID		1700
  
! DATA(insert OID = 1790 ( refcursor	   PGNSP PGUID -1 f b U f t \054 0	0 2201 textin textout textrecv textsend - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("reference to cursor (portal name)");
  #define REFCURSOROID	1790
  
  /* OIDS 2200 - 2299 */
! DATA(insert OID = 2201 ( _refcursor    PGNSP PGUID -1 f b A f t \054 0 1790 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  
! DATA(insert OID = 2202 ( regprocedure  PGNSP PGUID	4 t b N f t \054 0	 0 2207 regprocedurein regprocedureout regprocedurerecv regproceduresend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered procedure (with args)");
  #define REGPROCEDUREOID 2202
  
! DATA(insert OID = 2203 ( regoper	   PGNSP PGUID	4 t b N f t \054 0	 0 2208 regoperin regoperout regoperrecv regopersend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered operator");
  #define REGOPEROID		2203
  
! DATA(insert OID = 2204 ( regoperator   PGNSP PGUID	4 t b N f t \054 0	 0 2209 regoperatorin regoperatorout regoperatorrecv regoperatorsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered operator (with args)");
  #define REGOPERATOROID	2204
  
! DATA(insert OID = 2205 ( regclass	   PGNSP PGUID	4 t b N f t \054 0	 0 2210 regclassin regclassout regclassrecv regclasssend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered class");
  #define REGCLASSOID		2205
  
! DATA(insert OID = 2206 ( regtype	   PGNSP PGUID	4 t b N f t \054 0	 0 2211 regtypein regtypeout regtyperecv regtypesend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered type");
  #define REGTYPEOID		2206
  
! DATA(insert OID = 2207 ( _regprocedure PGNSP PGUID -1 f b A f t \054 0 2202 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 2208 ( _regoper	   PGNSP PGUID -1 f b A f t \054 0 2203 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 2209 ( _regoperator  PGNSP PGUID -1 f b A f t \054 0 2204 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 2210 ( _regclass	   PGNSP PGUID -1 f b A f t \054 0 2205 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 2211 ( _regtype	   PGNSP PGUID -1 f b A f t \054 0 2206 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  #define REGTYPEARRAYOID 2211
  
  /* uuid */
! DATA(insert OID = 2950 ( uuid			PGNSP PGUID 16 f b U f t \054 0 0 2951 uuid_in uuid_out uuid_recv uuid_send - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("UUID datatype");
! DATA(insert OID = 2951 ( _uuid			PGNSP PGUID -1 f b A f t \054 0 2950 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  
  /* text search */
! DATA(insert OID = 3614 ( tsvector		PGNSP PGUID -1 f b U f t \054 0 0 3643 tsvectorin tsvectorout tsvectorrecv tsvectorsend - - ts_typanalyze i x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("text representation for text search");
  #define TSVECTOROID		3614
! DATA(insert OID = 3642 ( gtsvector		PGNSP PGUID -1 f b U f t \054 0 0 3644 gtsvectorin gtsvectorout - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("GiST index internal text representation for text search");
  #define GTSVECTOROID	3642
! DATA(insert OID = 3615 ( tsquery		PGNSP PGUID -1 f b U f t \054 0 0 3645 tsqueryin tsqueryout tsqueryrecv tsquerysend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("query representation for text search");
  #define TSQUERYOID		3615
! DATA(insert OID = 3734 ( regconfig		PGNSP PGUID 4 t b N f t \054 0 0 3735 regconfigin regconfigout regconfigrecv regconfigsend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered text search configuration");
  #define REGCONFIGOID	3734
! DATA(insert OID = 3769 ( regdictionary	PGNSP PGUID 4 t b N f t \054 0 0 3770 regdictionaryin regdictionaryout regdictionaryrecv regdictionarysend - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("registered text search dictionary");
  #define REGDICTIONARYOID	3769
  
! DATA(insert OID = 3643 ( _tsvector		PGNSP PGUID -1 f b A f t \054 0 3614 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 3644 ( _gtsvector		PGNSP PGUID -1 f b A f t \054 0 3642 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 3645 ( _tsquery		PGNSP PGUID -1 f b A f t \054 0 3615 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 3735 ( _regconfig		PGNSP PGUID -1 f b A f t \054 0 3734 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
! DATA(insert OID = 3770 ( _regdictionary PGNSP PGUID -1 f b A f t \054 0 3769 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 -1 f _null_ _null_ ));
  
! DATA(insert OID = 2970 ( txid_snapshot	PGNSP PGUID -1 f b U f t \054 0 0 2949 txid_snapshot_in txid_snapshot_out txid_snapshot_recv txid_snapshot_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  DESCR("txid snapshot");
! DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  
  /*
   * pseudo-types
*************** DATA(insert OID = 2949 ( _txid_snapshot
*** 598,628 ****
   * but there is now support for it in records and arrays.  Perhaps we should
   * just treat it as a regular base type?
   */
! DATA(insert OID = 2249 ( record			PGNSP PGUID -1 f p P f t \054 0 0 2287 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
  #define RECORDOID		2249
! DATA(insert OID = 2287 ( _record		PGNSP PGUID -1 f p P f t \054 0 2249 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
  #define RECORDARRAYOID	2287
! DATA(insert OID = 2275 ( cstring		PGNSP PGUID -2 f p P f t \054 0 0 1263 cstring_in cstring_out cstring_recv cstring_send - - - c p f 0 -1 0 _null_ _null_ ));
  #define CSTRINGOID		2275
! DATA(insert OID = 2276 ( any			PGNSP PGUID  4 t p P f t \054 0 0 0 any_in any_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define ANYOID			2276
! DATA(insert OID = 2277 ( anyarray		PGNSP PGUID -1 f p P f t \054 0 0 0 anyarray_in anyarray_out anyarray_recv anyarray_send - - - d x f 0 -1 0 _null_ _null_ ));
  #define ANYARRAYOID		2277
! DATA(insert OID = 2278 ( void			PGNSP PGUID  4 t p P f t \054 0 0 0 void_in void_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define VOIDOID			2278
! DATA(insert OID = 2279 ( trigger		PGNSP PGUID  4 t p P f t \054 0 0 0 trigger_in trigger_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define TRIGGEROID		2279
! DATA(insert OID = 2280 ( language_handler	PGNSP PGUID  4 t p P f t \054 0 0 0 language_handler_in language_handler_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define LANGUAGE_HANDLEROID		2280
! DATA(insert OID = 2281 ( internal		PGNSP PGUID  SIZEOF_POINTER t p P f t \054 0 0 0 internal_in internal_out - - - - - ALIGNOF_POINTER p f 0 -1 0 _null_ _null_ ));
  #define INTERNALOID		2281
! DATA(insert OID = 2282 ( opaque			PGNSP PGUID  4 t p P f t \054 0 0 0 opaque_in opaque_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define OPAQUEOID		2282
! DATA(insert OID = 2283 ( anyelement		PGNSP PGUID  4 t p P f t \054 0 0 0 anyelement_in anyelement_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define ANYELEMENTOID	2283
! DATA(insert OID = 2776 ( anynonarray	PGNSP PGUID  4 t p P f t \054 0 0 0 anynonarray_in anynonarray_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define ANYNONARRAYOID	2776
! DATA(insert OID = 3500 ( anyenum		PGNSP PGUID  4 t p P f t \054 0 0 0 anyenum_in anyenum_out - - - - - i p f 0 -1 0 _null_ _null_ ));
  #define ANYENUMOID		3500
  
  
--- 612,642 ----
   * but there is now support for it in records and arrays.  Perhaps we should
   * just treat it as a regular base type?
   */
! DATA(insert OID = 2249 ( record			PGNSP PGUID -1 f p P f t \054 0 0 2287 record_in record_out record_recv record_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  #define RECORDOID		2249
! DATA(insert OID = 2287 ( _record		PGNSP PGUID -1 f p P f t \054 0 2249 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  #define RECORDARRAYOID	2287
! DATA(insert OID = 2275 ( cstring		PGNSP PGUID -2 f p P f t \054 0 0 1263 cstring_in cstring_out cstring_recv cstring_send - - - c p f 0 -1 0 -1 f _null_ _null_ ));
  #define CSTRINGOID		2275
! DATA(insert OID = 2276 ( any			PGNSP PGUID  4 t p P f t \054 0 0 0 any_in any_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define ANYOID			2276
! DATA(insert OID = 2277 ( anyarray		PGNSP PGUID -1 f p P f t \054 0 0 0 anyarray_in anyarray_out anyarray_recv anyarray_send - - - d x f 0 -1 0 -1 f _null_ _null_ ));
  #define ANYARRAYOID		2277
! DATA(insert OID = 2278 ( void			PGNSP PGUID  4 t p P f t \054 0 0 0 void_in void_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define VOIDOID			2278
! DATA(insert OID = 2279 ( trigger		PGNSP PGUID  4 t p P f t \054 0 0 0 trigger_in trigger_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define TRIGGEROID		2279
! DATA(insert OID = 2280 ( language_handler	PGNSP PGUID  4 t p P f t \054 0 0 0 language_handler_in language_handler_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define LANGUAGE_HANDLEROID		2280
! DATA(insert OID = 2281 ( internal		PGNSP PGUID  SIZEOF_POINTER t p P f t \054 0 0 0 internal_in internal_out - - - - - ALIGNOF_POINTER p f 0 -1 0 -1 f _null_ _null_ ));
  #define INTERNALOID		2281
! DATA(insert OID = 2282 ( opaque			PGNSP PGUID  4 t p P f t \054 0 0 0 opaque_in opaque_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define OPAQUEOID		2282
! DATA(insert OID = 2283 ( anyelement		PGNSP PGUID  4 t p P f t \054 0 0 0 anyelement_in anyelement_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define ANYELEMENTOID	2283
! DATA(insert OID = 2776 ( anynonarray	PGNSP PGUID  4 t p P f t \054 0 0 0 anynonarray_in anynonarray_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define ANYNONARRAYOID	2776
! DATA(insert OID = 3500 ( anyenum		PGNSP PGUID  4 t p P f t \054 0 0 0 anyenum_in anyenum_out - - - - - i p f 0 -1 0 -1 f _null_ _null_ ));
  #define ANYENUMOID		3500
  
  
diff --git a/src/include/catalog/pg_type_fn.h b/src/include/catalog/pg_type_fn.h
index cc72350..bc34568 100644
*** a/src/include/catalog/pg_type_fn.h
--- b/src/include/catalog/pg_type_fn.h
*************** extern Oid TypeCreate(Oid newTypeOid,
*** 50,56 ****
  		   char storage,
  		   int32 typeMod,
  		   int32 typNDims,
! 		   bool typeNotNull);
  
  extern void GenerateTypeDependencies(Oid typeNamespace,
  						 Oid typeObjectId,
--- 50,58 ----
  		   char storage,
  		   int32 typeMod,
  		   int32 typNDims,
!     	   bool typeNotNull,
! 		   int32 typeNLabels,
! 		   bool typeSorted);
  
  extern void GenerateTypeDependencies(Oid typeNamespace,
  						 Oid typeObjectId,
diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h
index 2bff7e1..9faf3ea 100644
*** a/src/include/commands/typecmds.h
--- b/src/include/commands/typecmds.h
*************** extern void RemoveTypes(DropStmt *drop);
*** 24,29 ****
--- 24,30 ----
  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 0d33a2e..139c8ed 100644
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
*************** typedef enum NodeTag
*** 348,354 ****
  	T_DropUserMappingStmt,
  	T_AlterTableSpaceOptionsStmt,
  	T_SecLabelStmt,
! 
  	/*
  	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
  	 */
--- 348,354 ----
  	T_DropUserMappingStmt,
  	T_AlterTableSpaceOptionsStmt,
  	T_SecLabelStmt,
! 	T_AlterEnumStmt,
  	/*
  	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
  	 */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ca225d0..1c3397e 100644
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
*************** typedef struct CreateEnumStmt
*** 2192,2197 ****
--- 2192,2211 ----
  
  
  /* ----------------------
+  *		Alter Type Statement, enum types
+  * ----------------------
+  */
+ typedef struct AlterEnumStmt
+ {
+ 	NodeTag		type;
+ 	List	   *typeName;		/* qualified name (list of Value strings) */
+ 	char	   *newVal;			/* new enum value */
+ 	char	   *newValNeighbour;/* neighbouring enum value */
+ 	bool	    newValIsAfter;	/* new enum value is after neighbour? */
+ } AlterEnumStmt;
+ 
+ 
+ /* ----------------------
   *		Create View Statement
   * ----------------------
   */
diff --git a/src/test/regress/expected/enum.out b/src/test/regress/expected/enum.out
index 56240c0..f3c86b1 100644
*** a/src/test/regress/expected/enum.out
--- b/src/test/regress/expected/enum.out
*************** ERROR:  invalid input value for enum rai
*** 25,30 ****
--- 25,122 ----
  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 typnlabels, typsorted
+ FROM pg_type
+ WHERE oid = 'planets'::regtype;
+  typnlabels | typsorted 
+ ------------+-----------
+           4 | t
+ (1 row)
+ 
+ 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 typnlabels, typsorted
+ FROM pg_type
+ WHERE oid = 'planets'::regtype;
+  typnlabels | typsorted 
+ ------------+-----------
+           8 | f
+ (1 row)
+ 
+ SELECT enumlabel, enumsortorder 
+ FROM pg_enum 
+ WHERE enumtypid = 'planets'::regtype
+ ORDER by CAST(enumlabel AS planets);
+  enumlabel | enumsortorder 
+ -----------+---------------
+  mercury   |             1
+  venus     |             2
+  earth     |             3
+  mars      |             4
+  jupiter   |             5
+  saturn    |             6
+  uranus    |             7
+  neptune   |             8
+ (8 rows)
+ 
+ select 'mars'::planets > 'mercury' as using_sortorder;
+  using_sortorder 
+ -----------------
+  t
+ (1 row)
+ 
+ \dT+ planets
+                         List of data types
+  Schema |  Name   | Internal name | Size | Elements | Description 
+ --------+---------+---------------+------+----------+-------------
+  public | planets | planets       | 4    | mercury +| 
+         |         |               |      | venus   +| 
+         |         |               |      | earth   +| 
+         |         |               |      | mars    +| 
+         |         |               |      | jupiter +| 
+         |         |               |      | saturn  +| 
+         |         |               |      | uranus  +| 
+         |         |               |      | neptune  | 
+ (1 row)
+ 
+ -- 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 label.
+ DROP TYPE planets;
+ --
  -- Basic table creation, row selection
  --
  CREATE TABLE enumtest (col rainbow);
*************** SELECT COUNT(*) FROM pg_type WHERE typna
*** 403,409 ****
  
  SELECT * FROM pg_enum WHERE NOT EXISTS
    (SELECT 1 FROM pg_type WHERE pg_type.oid = enumtypid);
!  enumtypid | enumlabel 
! -----------+-----------
  (0 rows)
  
--- 495,501 ----
  
  SELECT * FROM pg_enum WHERE NOT EXISTS
    (SELECT 1 FROM pg_type WHERE pg_type.oid = enumtypid);
!  enumtypid | enumlabel | enumsortorder 
! -----------+-----------+---------------
  (0 rows)
  
diff --git a/src/test/regress/sql/enum.sql b/src/test/regress/sql/enum.sql
index 387e8e7..6092706 100644
*** a/src/test/regress/sql/enum.sql
--- b/src/test/regress/sql/enum.sql
*************** SELECT 'red'::rainbow;
*** 16,21 ****
--- 16,71 ----
  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 typnlabels, typsorted
+ FROM pg_type
+ WHERE oid = 'planets'::regtype;
+ 
+ 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 typnlabels, typsorted
+ FROM pg_type
+ WHERE oid = 'planets'::regtype;
+ 
+ SELECT enumlabel, enumsortorder 
+ FROM pg_enum 
+ WHERE enumtypid = 'planets'::regtype
+ ORDER by CAST(enumlabel AS planets);
+ 
+ select 'mars'::planets > 'mercury' as using_sortorder;
+ 
+ \dT+ planets
+ 
+ -- errors for adding labels 
+ ALTER TYPE planets ADD 
+ 	  'plutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutopluto';
+ 
+ ALTER TYPE planets ADD 'pluto' AFTER 'zeus';
+ 
+ DROP TYPE planets;
+ --
  -- Basic table creation, row selection
  --
  CREATE TABLE enumtest (col rainbow);
#39Andrew Dunstan
andrew@dunslane.net
In reply to: Dean Rasheed (#38)
Re: WIP: extensible enums

On 10/15/2010 04:33 AM, Dean Rasheed wrote:

I started looking at this last night, but ran out of time. I'll
continue this evening / over the weekend. Here are my comments so far:

Patch applies cleanly to current git master with no offsets.
Compiles cleanly with no warnings.
Regression tests pass.

The regression tests look reasonable, but I'd like to see a test of
\dT+. Also it could be made to exercise the comparison function more
if the test query did an ORDER BY CAST(enumlabel as planets).

The docs for ALTER TYPE have been updated. I found a few minor typos,
and also a couple of other sections of the manual that needed
updating.

Attached is an updated version with these changes. Andrew, let me know
if you're happy with these tweaks and I'll continue reviewing over the
weekend.

Thanks for the review. This looks good.

cheers

andrew

#40Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Andrew Dunstan (#39)
Re: WIP: extensible enums

On 10/15/2010 04:33 AM, Dean Rasheed wrote:

I started looking at this last night, but ran out of time. I'll
continue this evening / over the weekend.

Continuing my review of this patch...

Usability review
----------------

What the patch does:

This patch adds syntax to allow additional enum values to be added to
an enum type, at any position in the list. The syntax has already been
discussed and a broad consensus reached. To me the new syntax seems
very logical and easy to use/remember.

Do we want that?

Yes, I think so. I can think of a few past projects where I could have
used this. A possible future extension would be the ability to remove
enum values, but that is likely to have additional complications, and
I think the number of use cases for it is smaller. I see no reason to
insist that it be part of this patch.

Do we already have it?

No.

Does it follow SQL spec, or the community-agreed behavior?

Not in the SQL spec, but it does follow agreed behaviour.

Does it include pg_dump support (if applicable)?

Yes.

Are there dangers?

None that I can think of.

Have all the bases been covered?

I just noticed that a couple of columns have been added to pg_type, so
there's another bit documentation that needs updating.

Feature test
------------

Does the feature work as advertised?

Yes.

Are there corner cases the author has failed to consider?

None that I could find.

Are there any assertion failures or crashes?

Yes, I'm afraid I managed to provoke a crash by running the regression
tests with -DCATCACHE_FORCE_RELEASE, after spotting a suspicious bit
of code (see below).

Performance review
------------------

Does the patch slow down simple tests?

There is a documented performance penalty when working with enum
values that are not in OID order. To attempt to measure this I created
2 tables, foo and bar, each with 800,000 rows. foo had an enum column
using the planets enum from the regression test, with values not in
OID order. bar had a similar enum column, but with values in the
default OID order.

Performance differences for comparison-heavy operations are most noticable:

SELECT MAX(p) FROM foo;
max
---------
neptune
(1 row)

Time: 132.305 ms

SELECT MAX(p) FROM bar;
max
---------
neptune
(1 row)

Time: 93.313 ms

SELECT p FROM foo ORDER BY p OFFSET 500000 LIMIT 1;
p
--------
saturn
(1 row)

Time: 1516.725 ms

SELECT p FROM bar ORDER BY p OFFSET 500000 LIMIT 1;
p
--------
saturn
(1 row)

Time: 1043.010 ms

(optimised build, asserts off)

That's a bigger performance hit than a I would have expected, even
though it is documented.

enum_ccmp() is using bsearch(), so there's a fair amount of function
call overhead there, and perhaps for small enums, a simple linear scan
would be faster. I'm not sure to what extent this is worth worrying
about.

Code review
-----------

I'm not familar enough with the pg_upgrade code to really comment on it.

Looking at AddEnumLabel() I found it a bit hard to follow, but near
the end of the block used when BEFORE/AFTER is specified, it does
this:

ReleaseCatCacheList(list);

/* are the labels sorted by OID? */
if (result && newelemorder > 1)
result = newOid > HeapTupleGetOid(existing[newelemorder-2]);
if (result && newelemorder < nelems + 1)
result = newOid < HeapTupleGetOid(existing[newelemorder-1]);

It looks to me as though 'existing[...]' is a pointer into a member of
the list that's just been freed, so it risks reading freed memory.
That seems to be confirmed by running the tests with
-DCATCACHE_FORCE_RELEASE. Doing so causes a number of the tests to
fail/crash, but I didn't dig any deeper to confirm that this was the
cause.

For the most part, this patch looks good, but I think there is still a
bit of tidying up to do.

Regards,
Dean

#41Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Dean Rasheed (#40)
Re: WIP: extensible enums

On 16 October 2010 18:25, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

Are there corner cases the author has failed to consider?

None that I could find.

Are there any assertion failures or crashes?

I just thought of another corner case, which can lead to a crash. The
comparison code assumes that the number of elements in the enumeration
is constant during a query, but that's not necessarily the case. For
example the following will crash it:

CREATE TYPE test_enum AS ENUM('Elem 1');

CREATE OR REPLACE FUNCTION test_fn(a int) RETURNS test_enum AS
$$
DECLARE
new_label text;
BEGIN
new_label := 'Elem '||a;
EXECUTE 'ALTER TYPE test_enum ADD '''||new_label||''' BEFORE ''Elem 1''';
RETURN new_label::test_enum;
END;
$$
LANGUAGE plpgsql;

WITH t(a) AS (SELECT test_fn(i) FROM generate_series(2, 10) g(i))
SELECT MAX(a) from t;

Of course that's a pathalogical example, but we should protect against
it, preferrably without compromising performance in more normal cases.

Regards,
Dean

#42Andrew Dunstan
andrew@dunslane.net
In reply to: Dean Rasheed (#41)
Re: WIP: extensible enums

On 10/17/2010 05:30 AM, Dean Rasheed wrote:

On 16 October 2010 18:25, Dean Rasheed<dean.a.rasheed@gmail.com> wrote:

Are there corner cases the author has failed to consider?

None that I could find.

Are there any assertion failures or crashes?

I just thought of another corner case, which can lead to a crash. The
comparison code assumes that the number of elements in the enumeration
is constant during a query, but that's not necessarily the case. For
example the following will crash it:

CREATE TYPE test_enum AS ENUM('Elem 1');

CREATE OR REPLACE FUNCTION test_fn(a int) RETURNS test_enum AS
$$
DECLARE
new_label text;
BEGIN
new_label := 'Elem '||a;
EXECUTE 'ALTER TYPE test_enum ADD '''||new_label||''' BEFORE ''Elem 1''';
RETURN new_label::test_enum;
END;
$$
LANGUAGE plpgsql;

WITH t(a) AS (SELECT test_fn(i) FROM generate_series(2, 10) g(i))
SELECT MAX(a) from t;

Of course that's a pathalogical example, but we should protect against
it, preferrably without compromising performance in more normal cases.

Yeah, good point. But how do we manage that? Looking up the number of
elements on each function call will cause a performance degradation, I
suspect. I'll think about it, but if you have any ideas please speak up.
I'm fairly sure we should also recheck the cached sorted property for
the same reason, incidentally.

cheers

andrew

#43Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#42)
Re: WIP: extensible enums

Andrew Dunstan <andrew@dunslane.net> writes:

On 10/17/2010 05:30 AM, Dean Rasheed wrote:

I just thought of another corner case, which can lead to a crash. The
comparison code assumes that the number of elements in the enumeration
is constant during a query, but that's not necessarily the case.
...
Of course that's a pathalogical example, but we should protect against
it, preferrably without compromising performance in more normal cases.

Yeah, good point. But how do we manage that?

Why is it crashing? I can see that this sort of thing might lead to
nonsensical answers, but a crash is harder to understand.

regards, tom "haven't read the patch" lane

#44Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Tom Lane (#43)
Re: WIP: extensible enums

On 17 October 2010 15:38, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Andrew Dunstan <andrew@dunslane.net> writes:

On 10/17/2010 05:30 AM, Dean Rasheed wrote:

I just thought of another corner case, which can lead to a crash. The
comparison code assumes that the number of elements in the enumeration
is constant during a query, but that's not necessarily the case.
...
Of course that's a pathalogical example, but we should protect against
it, preferrably without compromising performance in more normal cases.

Yeah, good point. But how do we manage that?

Why is it crashing?  I can see that this sort of thing might lead to
nonsensical answers, but a crash is harder to understand.

Hmm, it's harder than I thought. The crash is because each time it
finds a label it hasn't seen before it adds it to the array of cached
values without checking the array bounds. That array is only as big as
the number of elements in the enum the first time it was called.
Allowing the array to grow would prevent crashes, but not protect
again returning incorrect answers.

Perhaps it should just read and cache all the values the first time it
is called. Then if it ever fails to find a value in the array, it
knows that the enum must have grown, and it can rebuild the whole
array.

Regards,
Dean

Show quoted text

                       regards, tom "haven't read the patch" lane

#45Andrew Dunstan
andrew@dunslane.net
In reply to: Tom Lane (#43)
Re: WIP: extensible enums

On 10/17/2010 10:38 AM, Tom Lane wrote:

Andrew Dunstan<andrew@dunslane.net> writes:

On 10/17/2010 05:30 AM, Dean Rasheed wrote:

I just thought of another corner case, which can lead to a crash. The
comparison code assumes that the number of elements in the enumeration
is constant during a query, but that's not necessarily the case.
...
Of course that's a pathalogical example, but we should protect against
it, preferrably without compromising performance in more normal cases.

Yeah, good point. But how do we manage that?

Why is it crashing? I can see that this sort of thing might lead to
nonsensical answers, but a crash is harder to understand.

regards, tom "haven't read the patch" lane

Heh.

I've been deep in buildfarm work, but I'll look at this now to see what
I can find.

cheers

andrew

#46Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dean Rasheed (#44)
Re: WIP: extensible enums

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

Hmm, it's harder than I thought. The crash is because each time it
finds a label it hasn't seen before it adds it to the array of cached
values without checking the array bounds. That array is only as big as
the number of elements in the enum the first time it was called.

[ scratches head... ] And where does it get that number of elements
from, if not by doing the same work that would allow it to fill the
array completely? Something seems ill-designed here.

Allowing the array to grow would prevent crashes, but not protect
again returning incorrect answers.

Well, one of the questions here is exactly how wrong the answers can
be. Offhand, it seems to me that comparisons of two existing entries
can never be falsified by adding a new entry, so I'm not seeing that
there could be any real problem. If we allowed rearrangement of the
sort order of existing entries, it'd be problematic.

Perhaps it should just read and cache all the values the first time it
is called. Then if it ever fails to find a value in the array, it
knows that the enum must have grown, and it can rebuild the whole
array.

This is kept in typcache, right? ISTM the right thing to do is arrange
to invalidate the cached array when a cache flush event occurs, and
rebuild the whole array on next use. Then you just throw an error if
you're passed a value that isn't there.

regards, tom lane

#47Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Tom Lane (#46)
Re: WIP: extensible enums

On 17 October 2010 16:49, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

Hmm, it's harder than I thought. The crash is because each time it
finds a label it hasn't seen before it adds it to the array of cached
values without checking the array bounds. That array is only as big as
the number of elements in the enum the first time it was called.

[ scratches head... ]  And where does it get that number of elements
from, if not by doing the same work that would allow it to fill the
array completely?  Something seems ill-designed here.

Hmm. That's coming from a new column added to pg_type (typnlabels).
Perhaps that's not safe though. Are there potential race conditions
there?

Allowing the array to grow would prevent crashes, but not protect
again returning incorrect answers.

Well, one of the questions here is exactly how wrong the answers can
be.  Offhand, it seems to me that comparisons of two existing entries
can never be falsified by adding a new entry, so I'm not seeing that
there could be any real problem.  If we allowed rearrangement of the
sort order of existing entries, it'd be problematic.

Perhaps it should just read and cache all the values the first time it
is called. Then if it ever fails to find a value in the array, it
knows that the enum must have grown, and it can rebuild the whole
array.

This is kept in typcache, right?  ISTM the right thing to do is arrange
to invalidate the cached array when a cache flush event occurs, and
rebuild the whole array on next use.  Then you just throw an error if
you're passed a value that isn't there.

Makes sense.

Regards,
Dean

Show quoted text

                       regards, tom lane

#48Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dean Rasheed (#47)
Re: WIP: extensible enums

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

On 17 October 2010 16:49, Tom Lane <tgl@sss.pgh.pa.us> wrote:

[ scratches head... ] �And where does it get that number of elements
from, if not by doing the same work that would allow it to fill the
array completely? �Something seems ill-designed here.

Hmm. That's coming from a new column added to pg_type (typnlabels).
Perhaps that's not safe though. Are there potential race conditions
there?

I knew I shoulda read this patch ;-). That seems a lot more invasive
than this feature justifies. And I share your qualms about whether it's
race-condition-proof. We don't have very much locking on pg_type
entries, so making a hard assumption about consistency between two
different catalogs seems pretty risky.

The way I'd be inclined to design this is that altering an enum doesn't
change its pg_type entry at all, just add another row to pg_enum.
When first needing to compare values of an enum, load up the typcache
entry for it. This involves scanning all the entries for that type OID
in pg_enum, and determining from that whether you can compare the easy
way or not. If not, build the array that tells you how to sort, and put
it in the typcache entry.

The missing piece in this is how to determine when the typcache entry
has to be flushed. That should be driven by sinval signalling. There
are two different ways you could do it:

1. Watch for SI events on pg_enum. The problem here is we don't have
a syscache on pg_enum, and there's no obvious other reason to want one.
Also I think you'd have to flush *all* enum typcache entries, since you
couldn't always tell which one had been modified.

2. Watch for SI events on the pg_type row. However, since according
to what I said above an ALTER TYPE wouldn't actually modify the pg_type
row, you'd need to have the ALTER send out a "dummy" SI event. This
is not hard to do --- we have the concept of dummy inval events on
relations already --- but it's a bit ugly because of the risk of
omission. But the number of places that would need to do this seems
small, so I don't think that risk is large.

On balance I like the second idea better.

regards, tom lane

#49Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#48)
Re: WIP: extensible enums

I wrote:

The missing piece in this is how to determine when the typcache entry
has to be flushed. That should be driven by sinval signalling.

On reflection that doesn't seem good enough. Immediately after someone
else has committed an ALTER TYPE, your typcache entry is out of date,
and won't be updated until you get around to noticing the SI signal.
I was thinking that wouldn't matter because you'd never need to make a
comparison involving the new enum value --- it couldn't be in any table
rows you'd see as committed good. But this is wrong because you might
have to make index comparisons involving the new value, even before you
consider that the rows the index entries reference are good.

We could fix that with Dean's idea of reloading the cache whenever
we see that we are being asked to compare a value we don't have in the
cache entry. However, that assumes that we even notice that it's not
in the cache entry. If we're trying to use "fast" comparison then we
wouldn't notice that.

So the hard part of this really is to force other backends to switch
from "fast" to "slow" comparison in a timely fashion when an ALTER makes
that necessary. Right offhand I don't see any good way to do that,
at least not while having the "fast" method as fast as it is now.

regards, tom lane

#50Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Tom Lane (#49)
Re: WIP: extensible enums

On 17 October 2010 18:53, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I wrote:

The missing piece in this is how to determine when the typcache entry
has to be flushed.  That should be driven by sinval signalling.

On reflection that doesn't seem good enough.  Immediately after someone
else has committed an ALTER TYPE, your typcache entry is out of date,
and won't be updated until you get around to noticing the SI signal.
I was thinking that wouldn't matter because you'd never need to make a
comparison involving the new enum value --- it couldn't be in any table
rows you'd see as committed good.  But this is wrong because you might
have to make index comparisons involving the new value, even before you
consider that the rows the index entries reference are good.

We could fix that with Dean's idea of reloading the cache whenever
we see that we are being asked to compare a value we don't have in the
cache entry.  However, that assumes that we even notice that it's not
in the cache entry.  If we're trying to use "fast" comparison then we
wouldn't notice that.

That makes me think maybe the "fast" and "slow" comparisons should
both be done the same way, having a cache so that we notice
immediately if we get a new value.

Obviously that's not going to be as fast as the current "fast" method,
but the question is, can it be made sufficiently close? I think the
current sort+bsearch method is always going to be significantly
slower, but perhaps a dedicated hash table algorithm might work.

Regards,
Dean

Show quoted text

So the hard part of this really is to force other backends to switch
from "fast" to "slow" comparison in a timely fashion when an ALTER makes
that necessary.  Right offhand I don't see any good way to do that,
at least not while having the "fast" method as fast as it is now.

                       regards, tom lane

#51Andrew Dunstan
andrew@dunslane.net
In reply to: Tom Lane (#48)
Re: WIP: extensible enums

On 10/17/2010 01:20 PM, Tom Lane wrote:

I knew I shoulda read this patch ;-). That seems a lot more invasive
than this feature justifies. And I share your qualms about whether it's
race-condition-proof. We don't have very much locking on pg_type
entries, so making a hard assumption about consistency between two
different catalogs seems pretty risky.

The way I'd be inclined to design this is that altering an enum doesn't
change its pg_type entry at all, just add another row to pg_enum.
When first needing to compare values of an enum, load up the typcache
entry for it. This involves scanning all the entries for that type OID
in pg_enum, and determining from that whether you can compare the easy
way or not. If not, build the array that tells you how to sort, and put
it in the typcache entry.

Perhaps mistakenly I wanted to avoid doing that as it would slow down a
retail comparison quite a lot, especially in the case of an enum with a
very large label set. That's why I put the sorted property and label
count in pg_type.

cheers

andrew

#52Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dean Rasheed (#50)
Re: WIP: extensible enums

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

On 17 October 2010 18:53, Tom Lane <tgl@sss.pgh.pa.us> wrote:

We could fix that with Dean's idea of reloading the cache whenever
we see that we are being asked to compare a value we don't have in the
cache entry. �However, that assumes that we even notice that it's not
in the cache entry. �If we're trying to use "fast" comparison then we
wouldn't notice that.

That makes me think maybe the "fast" and "slow" comparisons should
both be done the same way, having a cache so that we notice
immediately if we get a new value.

Actually ... the race conditions can be a lot worse than just a race.
Consider

begin;
alter type myenum add 'some-value';
insert into mytab values('some-value');
rollback;

If mytab has an index on the enum col, we now have an index entry that
contains an enum value that isn't valid according to anybody, and nobody
knows how to compare it. If that entry is near the root then the index
is hopelessly corrupt: no one can tell which way to descend when
comparing it to some valid value.

I think what this says is that we cannot allow any manipulations that
involve an uncommitted enum value. Probably the easiest way is to make
the ALTER TYPE operation disallowed-inside-transaction-block. That's
pretty ugly, but doesn't seem like a serious restriction in practice
(though for example it'd mean we couldn't use it in pg_dump).

I'm not sure if enforcing such a restriction helps much in terms of
managing cache invalidations. Even with that, it seems possible to
encounter index entries for values that you haven't yet noticed the
invalidation message for.

regards, tom lane

#53Andrew Dunstan
andrew@dunslane.net
In reply to: Dean Rasheed (#50)
Re: WIP: extensible enums

On 10/17/2010 02:19 PM, Dean Rasheed wrote:

That makes me think maybe the "fast" and "slow" comparisons should
both be done the same way, having a cache so that we notice
immediately if we get a new value.

Obviously that's not going to be as fast as the current "fast" method,
but the question is, can it be made sufficiently close? I think the
current sort+bsearch method is always going to be significantly
slower, but perhaps a dedicated hash table algorithm might work.

Making that as fast as "Is this sorted? If yes, compare the two oids" or
even acceptably slower seems likely to be a challenge. I thought about
the sort of approach you suggest initially and didn't come up with
anything that seemed likely to work well enough.

cheers

andrew

#54Andrew Dunstan
andrew@dunslane.net
In reply to: Tom Lane (#52)
Re: WIP: extensible enums

On 10/17/2010 03:17 PM, Tom Lane wrote:

Dean Rasheed<dean.a.rasheed@gmail.com> writes:

On 17 October 2010 18:53, Tom Lane<tgl@sss.pgh.pa.us> wrote:

We could fix that with Dean's idea of reloading the cache whenever
we see that we are being asked to compare a value we don't have in the
cache entry. However, that assumes that we even notice that it's not
in the cache entry. If we're trying to use "fast" comparison then we
wouldn't notice that.

That makes me think maybe the "fast" and "slow" comparisons should
both be done the same way, having a cache so that we notice
immediately if we get a new value.

Actually ... the race conditions can be a lot worse than just a race.
Consider

begin;
alter type myenum add 'some-value';
insert into mytab values('some-value');
rollback;

If mytab has an index on the enum col, we now have an index entry that
contains an enum value that isn't valid according to anybody, and nobody
knows how to compare it. If that entry is near the root then the index
is hopelessly corrupt: no one can tell which way to descend when
comparing it to some valid value.

I think what this says is that we cannot allow any manipulations that
involve an uncommitted enum value. Probably the easiest way is to make
the ALTER TYPE operation disallowed-inside-transaction-block. That's
pretty ugly, but doesn't seem like a serious restriction in practice
(though for example it'd mean we couldn't use it in pg_dump).

Even in binary upgrade mode?

cheers

andrew

#55Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#53)
Re: WIP: extensible enums

Andrew Dunstan <andrew@dunslane.net> writes:

Making that as fast as "Is this sorted? If yes, compare the two oids" or
even acceptably slower seems likely to be a challenge. I thought about
the sort of approach you suggest initially and didn't come up with
anything that seemed likely to work well enough.

The fundamental problem here is that we can't tell by examining an enum
value whether we have to apply the "fast" or "slow" comparison method.
But what if we could?

The sneaky idea that just came to me is to declare that even-numbered
OID values can be sorted by direct comparison, whereas odd-numbered OIDs
can't. It seems fairly easy to ensure that that property holds while
creating the values, as long as you don't mind "burning" OIDs: if you
get a value you don't like, just demand another one. Then, as long as
both OIDs involved in a comparison are even, you just do a direct
comparison and no cache entry is needed at all. When either is odd, you
know you need a cache entry. You can also tell whether an existing
cache entry is stale: if it doesn't contain both values then you need to
refresh it. If it does have both, then it's good enough for the
immediate purpose, even if there are other values it doesn't know
about. So with this design we don't actually have to watch for inval
events at all ... we just refresh the cache entry whenever a comparison
finds that that's needed.

The one problem I can see with this is that it's only partially
on-disk-compatible with existing enum types: it'll almost certainly
think that they require slow comparison, even when they don't.
Maybe that's acceptable.

regards, tom lane

#56Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#54)
Re: WIP: extensible enums

Andrew Dunstan <andrew@dunslane.net> writes:

On 10/17/2010 03:17 PM, Tom Lane wrote:

I think what this says is that we cannot allow any manipulations that
involve an uncommitted enum value. Probably the easiest way is to make
the ALTER TYPE operation disallowed-inside-transaction-block. That's
pretty ugly, but doesn't seem like a serious restriction in practice
(though for example it'd mean we couldn't use it in pg_dump).

Even in binary upgrade mode?

Binary upgrade can probably be treated as a special case.

regards, tom lane

#57Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#51)
Re: WIP: extensible enums

Andrew Dunstan <andrew@dunslane.net> writes:

On 10/17/2010 01:20 PM, Tom Lane wrote:

The way I'd be inclined to design this is that altering an enum doesn't
change its pg_type entry at all, just add another row to pg_enum.
When first needing to compare values of an enum, load up the typcache
entry for it.

Perhaps mistakenly I wanted to avoid doing that as it would slow down a
retail comparison quite a lot, especially in the case of an enum with a
very large label set. That's why I put the sorted property and label
count in pg_type.

Just going back to this point: I don't buy that argument at all.
If you have to consult pg_type to find out whether fast or slow
comparison is needed, you've already burned all the cycles required
for a cache lookup. The only way that a large typcache entry would
really be a performance issue compared to just consulting pg_type
is if it had to be refreshed a lot, which doesn't seem like a likely
problem.

regards, tom lane

#58Andrew Dunstan
andrew@dunslane.net
In reply to: Tom Lane (#55)
Re: WIP: extensible enums

On 10/17/2010 03:56 PM, Tom Lane wrote:

Andrew Dunstan<andrew@dunslane.net> writes:

Making that as fast as "Is this sorted? If yes, compare the two oids" or
even acceptably slower seems likely to be a challenge. I thought about
the sort of approach you suggest initially and didn't come up with
anything that seemed likely to work well enough.

The fundamental problem here is that we can't tell by examining an enum
value whether we have to apply the "fast" or "slow" comparison method.
But what if we could?

The sneaky idea that just came to me is to declare that even-numbered
OID values can be sorted by direct comparison, whereas odd-numbered OIDs
can't. It seems fairly easy to ensure that that property holds while
creating the values, as long as you don't mind "burning" OIDs: if you
get a value you don't like, just demand another one. Then, as long as
both OIDs involved in a comparison are even, you just do a direct
comparison and no cache entry is needed at all. When either is odd, you
know you need a cache entry. You can also tell whether an existing
cache entry is stale: if it doesn't contain both values then you need to
refresh it. If it does have both, then it's good enough for the
immediate purpose, even if there are other values it doesn't know
about. So with this design we don't actually have to watch for inval
events at all ... we just refresh the cache entry whenever a comparison
finds that that's needed.

Hmm, nice. What I like about this is that it's not an all or nothing
deal. If you add one label that needs an odd Oid, most of the
comparisons will still be fast.

I think the rule for choosing the Oid for the new entry would go
something like this:

* if we're adding a label at the end and the Oid of the last entry
is even, take the first Oid that is either even and greater than
the Oid of the last entry, or odd and less than the Oid of the
last entry
* for all other positions take the first odd Oid

The one problem I can see with this is that it's only partially
on-disk-compatible with existing enum types: it'll almost certainly
think that they require slow comparison, even when they don't.
Maybe that's acceptable.

Yeah, not sure about that.

cheers

andrew

#59Greg Stark
gsstark@mit.edu
In reply to: Tom Lane (#52)
Re: WIP: extensible enums

On Sun, Oct 17, 2010 at 12:17 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

       begin;
       alter type myenum add 'some-value';
       insert into mytab values('some-value');
       rollback;

....

I think what this says is that we cannot allow any manipulations that
involve an uncommitted enum value.  Probably the easiest way is to make
the ALTER TYPE operation disallowed-inside-transaction-block.  That's
pretty ugly, but doesn't seem like a serious restriction in practice
(though for example it'd mean we couldn't use it in pg_dump).

I fear this is the camel's nose under the door towards making all DDL
non-transactional a la Oracle.

The alternative is that there are two steps to creating an enum. A
low-level modification which adds the new value and its collation
value to the list of valid things to compare. This would be done as
something like an autonomous transaction and be committed regardless
of whether the outer transaction commits. It wouldn't add the value to
the user-visible list of values or allowable values for inserting.
Only once that was committed could we then make the transactional
modification to the user visible DDL.

The main problem with this is of course that we don't actually have
autonomous transactions...

--
greg

#60Andrew Dunstan
andrew@dunslane.net
In reply to: Tom Lane (#55)
Re: WIP: extensible enums

On 10/17/2010 03:56 PM, Tom Lane wrote:

[clever scheme to treat even numbered enum Oids as sorted]

The one problem I can see with this is that it's only partially
on-disk-compatible with existing enum types: it'll almost certainly
think that they require slow comparison, even when they don't.
Maybe that's acceptable.

Thinking more about this, could we do some sort of hybrid scheme that
stashes the 'oids are sorted correctly' property of the type somewhat
like what I proposed?

Binary upgrade would be mildly tricky but not impossible.

cheers

andrew

#61Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#60)
Re: WIP: extensible enums

Andrew Dunstan <andrew@dunslane.net> writes:

On 10/17/2010 03:56 PM, Tom Lane wrote:

[clever scheme to treat even numbered enum Oids as sorted]
The one problem I can see with this is that it's only partially
on-disk-compatible with existing enum types: it'll almost certainly
think that they require slow comparison, even when they don't.
Maybe that's acceptable.

Thinking more about this, could we do some sort of hybrid scheme that
stashes the 'oids are sorted correctly' property of the type somewhat
like what I proposed?

I think that fundamentally doesn't work in the face of a concurrent
ALTER TYPE. And even if it did work, by the time you've done the
cache lookup to check your stashed flag, what have you really saved?
The only way to have comparisons that are on par with current
performance is to not need to do any lookup at all.

The scenario that seems the most dangerous to me is

1. Begin transaction T1.
2. T1 does cache lookup, or any other pushup you want, and
decides that the enum type is correctly sorted.
3. T2 commits an ALTER TYPE that adds a non-sorted OID.
4. T3 inserts that OID in an index.
5. T1 encounters the out-of-order OID in the index.

If T1 is not able to recognize that the OID it's looking at is not
in-order, despite its previously cached information, it's going to lose
badly. It's impractical to do an AcceptInvalidationMessages on every
single comparison, so AFAICT you *have to* be able to deal correctly
with enum values that were added since your cache entry was made.

We could possibly deal with enum types that follow the existing
convention if we made the cache entry hold a list of all the original,
known-to-be-sorted OIDs. (This could be reasonably compact and cheap to
probe if it were represented as a starting OID and a Bitmapset of delta
values, since we can assume that the initial set of OIDs is pretty close
together.) But we have to have that cache entry, and we have to consult
it on every single comparison, so it's definitely going to be slower
than before.

So I'm thinking the comparison procedure goes like this:

1. Both OIDs even?
If so, just compare them numerically, and we're done.

2. Lookup cache entry for enum type.

3. Both OIDs in list of known-sorted OIDs?
If so, just compare them numerically, and we're done.

4. Search the part of the cache entry that lists sort positions.
If not both present, refresh the cache entry.
If still not present, throw error.

5. Compare by sort positions.

Step 4 is the slowest part but would be avoided in most cases.
However, step 2 is none too speedy either, and would usually
be required when dealing with pre-existing enums.

regards, tom lane

#62Andrew Dunstan
andrew@dunslane.net
In reply to: Tom Lane (#61)
Re: WIP: extensible enums

On 10/18/2010 10:52 AM, Tom Lane wrote:

We could possibly deal with enum types that follow the existing
convention if we made the cache entry hold a list of all the original,
known-to-be-sorted OIDs. (This could be reasonably compact and cheap to
probe if it were represented as a starting OID and a Bitmapset of delta
values, since we can assume that the initial set of OIDs is pretty close
together.)

How are we going to know what those are?

We could keep track of the beginning and end of the original range maybe
and refuse to create any new enum values for the type inside that range.
That might make binary upgrade a bit ugly, but it's probably manageable
even so.

cheers

andrew

#63Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#62)
Re: WIP: extensible enums

Andrew Dunstan <andrew@dunslane.net> writes:

On 10/18/2010 10:52 AM, Tom Lane wrote:

We could possibly deal with enum types that follow the existing
convention if we made the cache entry hold a list of all the original,
known-to-be-sorted OIDs. (This could be reasonably compact and cheap to
probe if it were represented as a starting OID and a Bitmapset of delta
values, since we can assume that the initial set of OIDs is pretty close
together.)

How are we going to know what those are?

You read pg_enum while constructing the cache entry, sort by nominal
sort position, and look to see how many OIDs form a sorted and reasonably
compact range. I don't see a need to know which of those OIDs were
actually original; you can get close enough with heuristic reverse
engineering. The only real limitation is not wanting the bitmapset to
get too enormous, so you might often be able to incorporate OIDs that
in fact weren't original, so long as they chanced to be OK order-wise.
This'd be a win anytime it saved you from having to proceed to step 4
in comparisons.

regards, tom lane

#64Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Tom Lane (#61)
Re: WIP: extensible enums

On 18 October 2010 15:52, Tom Lane <tgl@sss.pgh.pa.us> wrote:

So I'm thinking the comparison procedure goes like this:

1. Both OIDs even?
       If so, just compare them numerically, and we're done.

2. Lookup cache entry for enum type.

3. Both OIDs in list of known-sorted OIDs?
       If so, just compare them numerically, and we're done.

4. Search the part of the cache entry that lists sort positions.
       If not both present, refresh the cache entry.
       If still not present, throw error.

5. Compare by sort positions.

Step 4 is the slowest part but would be avoided in most cases.
However, step 2 is none too speedy either, and would usually
be required when dealing with pre-existing enums.

I was thinking that steps 2-5 could be sped up by doing something like:

2. If first time in:
Build a lightweight hash map: [ oid -> sort position ] with
all the enum values

3. Look up each oid in the hash map
If not both present, rebuild the hash map
If still not present, throw error.

4. Compare by sort positions.

I think the hash map lookups could be made pretty quick, if we're
prepared to write a bit of dedicated code there rather than relying on
the more general-purpose caching code.

Regards,
Dean

Show quoted text

                       regards, tom lane

#65Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dean Rasheed (#64)
Re: WIP: extensible enums

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

I think the hash map lookups could be made pretty quick, if we're
prepared to write a bit of dedicated code there rather than relying on
the more general-purpose caching code.

It's still going to be very significantly slower than what I'm
suggesting --- I'm *already* assuming that step 4 is using a hash
or something else smarter than just traversing a list. My step 3
is just a bit-array index operation (well, 2 of 'em).

(I'm pretty dubious of unsubstantiated claims that you can build a hash
table that's significantly faster than our existing hash code, anyway.)

regards, tom lane

#66Andrew Dunstan
andrew@dunslane.net
In reply to: Dean Rasheed (#64)
Re: WIP: extensible enums

On 10/18/2010 01:40 PM, Dean Rasheed wrote:

On 18 October 2010 15:52, Tom Lane<tgl@sss.pgh.pa.us> wrote:

So I'm thinking the comparison procedure goes like this:

1. Both OIDs even?
If so, just compare them numerically, and we're done.

2. Lookup cache entry for enum type.

3. Both OIDs in list of known-sorted OIDs?
If so, just compare them numerically, and we're done.

4. Search the part of the cache entry that lists sort positions.
If not both present, refresh the cache entry.
If still not present, throw error.

5. Compare by sort positions.

Step 4 is the slowest part but would be avoided in most cases.
However, step 2 is none too speedy either, and would usually
be required when dealing with pre-existing enums.

I was thinking that steps 2-5 could be sped up by doing something like:

2. If first time in:
Build a lightweight hash map: [ oid -> sort position ] with
all the enum values

3. Look up each oid in the hash map
If not both present, rebuild the hash map
If still not present, throw error.

4. Compare by sort positions.

I think the hash map lookups could be made pretty quick, if we're
prepared to write a bit of dedicated code there rather than relying on
the more general-purpose caching code.

If you have want to work on it and prove it's going to be better, please
do. I'm not convinced it will do a whole lot better than a binary search
that in most cases will do no more than a handful of probes.

cheers

andrew

#67Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#66)
Re: WIP: extensible enums

Andrew Dunstan <andrew@dunslane.net> writes:

If you have want to work on it and prove it's going to be better, please
do. I'm not convinced it will do a whole lot better than a binary search
that in most cases will do no more than a handful of probes.

Yeah, that's a good point. There's a range of table sizes where hashing
is faster than binary search, but I'm not sure that typical enums will
fall into that range.

regards, tom lane

#68Andrew Dunstan
andrew@dunslane.net
In reply to: Tom Lane (#61)
1 attachment(s)
Re: WIP: extensible enums

On 10/18/2010 10:52 AM, Tom Lane wrote:

We could possibly deal with enum types that follow the existing
convention if we made the cache entry hold a list of all the original,
known-to-be-sorted OIDs. (This could be reasonably compact and cheap to
probe if it were represented as a starting OID and a Bitmapset of delta
values, since we can assume that the initial set of OIDs is pretty close
together.) But we have to have that cache entry, and we have to consult
it on every single comparison, so it's definitely going to be slower
than before.

So I'm thinking the comparison procedure goes like this:

1. Both OIDs even?
If so, just compare them numerically, and we're done.

2. Lookup cache entry for enum type.

3. Both OIDs in list of known-sorted OIDs?
If so, just compare them numerically, and we're done.

4. Search the part of the cache entry that lists sort positions.
If not both present, refresh the cache entry.
If still not present, throw error.

5. Compare by sort positions.

Step 4 is the slowest part but would be avoided in most cases.
However, step 2 is none too speedy either, and would usually
be required when dealing with pre-existing enums.

OK, I've made adjustments that I think do what you're suggesting.

Patch is attached.

Alternatively this can be pulled from
<git@github.com:adunstan/postgresql-dev.git>

cheers

andrew

Attachments:

venum6.patchtext/x-patch; name=venum6.patchDownload
*** a/contrib/pg_upgrade/function.c
--- b/contrib/pg_upgrade/function.c
***************
*** 61,85 **** install_support_functions(migratorContext *ctx)
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 				"		binary_upgrade.set_next_heap_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_toast_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_index_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			 "		binary_upgrade.add_pg_enum_label(OID, OID, NAME) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
--- 61,85 ----
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 					 "		binary_upgrade.set_next_pg_enum_oid(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 				"		binary_upgrade.set_next_heap_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_toast_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_index_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
*** a/contrib/pg_upgrade_support/pg_upgrade_support.c
--- b/contrib/pg_upgrade_support/pg_upgrade_support.c
***************
*** 30,35 **** PG_MODULE_MAGIC;
--- 30,36 ----
  extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
  extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_array_oid;
  extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_toast_oid;
+ extern PGDLLIMPORT Oid binary_upgrade_next_pg_enum_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;
***************
*** 37,54 **** extern PGDLLIMPORT Oid binary_upgrade_next_index_relfilenode;
  Datum		set_next_pg_type_oid(PG_FUNCTION_ARGS);
  Datum		set_next_pg_type_array_oid(PG_FUNCTION_ARGS);
  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);
  
  PG_FUNCTION_INFO_V1(set_next_pg_type_oid);
  PG_FUNCTION_INFO_V1(set_next_pg_type_array_oid);
  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);
  
  Datum
  set_next_pg_type_oid(PG_FUNCTION_ARGS)
--- 38,55 ----
  Datum		set_next_pg_type_oid(PG_FUNCTION_ARGS);
  Datum		set_next_pg_type_array_oid(PG_FUNCTION_ARGS);
  Datum		set_next_pg_type_toast_oid(PG_FUNCTION_ARGS);
+ Datum       set_next_pg_enum_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);
  
  PG_FUNCTION_INFO_V1(set_next_pg_type_oid);
  PG_FUNCTION_INFO_V1(set_next_pg_type_array_oid);
  PG_FUNCTION_INFO_V1(set_next_pg_type_toast_oid);
+ PG_FUNCTION_INFO_V1(set_next_pg_enum_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);
  
  Datum
  set_next_pg_type_oid(PG_FUNCTION_ARGS)
***************
*** 81,86 **** set_next_pg_type_toast_oid(PG_FUNCTION_ARGS)
--- 82,97 ----
  }
  
  Datum
+ set_next_pg_enum_oid(PG_FUNCTION_ARGS)
+ {
+ 	Oid			enumoid = PG_GETARG_OID(0);
+ 
+ 	binary_upgrade_next_pg_enum_oid = enumoid;
+ 
+ 	PG_RETURN_VOID();
+ }
+ 
+ Datum
  set_next_heap_relfilenode(PG_FUNCTION_ARGS)
  {
  	Oid			relfilenode = PG_GETARG_OID(0);
***************
*** 110,124 **** set_next_index_relfilenode(PG_FUNCTION_ARGS)
  	PG_RETURN_VOID();
  }
  
- Datum
- add_pg_enum_label(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);
- 
- 	PG_RETURN_VOID();
- }
--- 121,123 ----
*** a/doc/src/sgml/ref/alter_type.sgml
--- b/doc/src/sgml/ref/alter_type.sgml
***************
*** 23,33 **** 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> 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>
  
  <phrase>where <replaceable class="PARAMETER">action</replaceable> is one of:</phrase>
  
--- 23,34 ----
  
   <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> 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,108 **** ALTER TYPE <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <replace
--- 104,131 ----
       </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 sort order is not set using <literal>BEFORE</literal> or 
+ 	  <literal>AFTER</literal>, then the new item is placed at the end of
+ 	  the list of values.
+      </para>
+ 	 <note>
+ 	  <para>
+ 	   Adding a new enum value will in some cases lower the comparison and 
+ 	   sorting performance of the enum type. This will only usually occur if  
+ 	   <literal>BEFORE</literal> or <literal>AFTER</literal> are used to set 
+ 	   the new value's sort order position somewhere other than at the end 
+ 	   of the list. Optimal performance will be restored if the database is 
+ 	   dumped and reloaded.
+ 	  </para>
+ 	 </note>
+     </listitem>
+    </varlistentry>
    </variablelist>
    </para>
  
***************
*** 196,201 **** ALTER TYPE <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <replace
--- 219,254 ----
        </listitem>
       </varlistentry>
  
+      <varlistentry>
+       <term><replaceable class="PARAMETER">data_type</replaceable></term>
+       <listitem>
+        <para>
+         The data type of the attribute to add, or the new type of the
+         attribute to alter.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
+      <varlistentry>
+       <term><replaceable class="PARAMETER">new_enum_value</replaceable></term>
+       <listitem>
+        <para>
+         The new value to be added to the num type's list of values. Like all
+ 		enum literals it needs to be quoted.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
+      <varlistentry>
+       <term><replaceable class="PARAMETER">exisiting_enum_value</replaceable></term>
+       <listitem>
+        <para>
+         The neighbour of the new value to be added to the num type's list of values. Like all
+ 		enum literals it needs to be quoted.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
      </variablelist>
     </para>
    </refsect1>
***************
*** 232,237 **** ALTER TYPE email SET SCHEMA customers;
--- 285,297 ----
  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>
  
   <refsect1>
*** a/src/backend/catalog/pg_enum.c
--- b/src/backend/catalog/pg_enum.c
***************
*** 18,29 ****
--- 18,226 ----
  #include "catalog/catalog.h"
  #include "catalog/indexing.h"
  #include "catalog/pg_enum.h"
+ #include "catalog/pg_type.h"
+ #include "utils/lsyscache.h"
+ #include "utils/syscache.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
  #include "utils/rel.h"
  #include "utils/tqual.h"
  
  static int	oid_cmp(const void *p1, const void *p2);
+ static int	sort_order_cmp(const void *p1, const void *p2);
+ 
+ Oid      binary_upgrade_next_pg_enum_oid = InvalidOid;
+ 
+ /*
+  * 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, 
+ 			 char *newVal, 
+ 			 char *neighbour, 
+ 			 bool newValIsAfter)
+ {
+ 	Oid        newOid;
+ 	Relation   pg_enum;
+ 	TupleDesc	tupDesc;
+ 	Datum		values[Natts_pg_enum];
+ 	bool		nulls[Natts_pg_enum];
+ 	NameData	enumlabel;
+ 	HeapTuple   enum_tup;
+ 	int         newelemorder;
+ 	CatCList   *list;
+ 	int nelems;
+ 
+ 	/* 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)));
+ 
+ 	/* get a new OID for the label */
+ 	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
+ 
+ 	list = SearchSysCacheList1(ENUMTYPOIDNAME, 
+ 							   ObjectIdGetDatum(enumTypeOid));
+ 	nelems =  list->n_members;
+ 
+ 	if (neighbour == NULL)
+ 	{
+ 		/* 
+ 		 * Put the new label at the end of the list.
+ 		 * No change to existing tuples is required.
+ 		 */
+ 		newelemorder = nelems + 1;
+ 	}
+ 	else 
+ 	{
+ 		/* BEFORE or AFTER specified */
+ 		int			i;
+ 		HeapTuple    *existing;
+ 		HeapTuple     nbr = NULL;
+ 		Form_pg_enum nbr_en;
+ 
+ 		/* sort the list of the existing elements by enumsortorder */
+ 		existing = palloc(nelems * sizeof(HeapTuple));
+ 
+ 		for (i = 0; i < nelems; i++)
+ 			existing[i] = &(list->members[i]->tuple);
+ 
+ 		qsort(existing, nelems, sizeof(HeapTuple), sort_order_cmp);
+ 		
+ 		/* locate the neighbour element */
+ 		for (i = 0; i < nelems; i++)
+ 		{
+ 			Form_pg_enum exists_en;
+ 			exists_en = (Form_pg_enum) GETSTRUCT(existing[i]);
+ 			if (strcmp(NameStr(exists_en->enumlabel),neighbour) == 0)
+ 				nbr = existing[i];
+ 
+ 		}
+ 
+ 		if (nbr == NULL)
+ 		{
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("\"%s\" is not an existing label.", neighbour)));
+ 		}
+ 
+ 		nbr_en = (Form_pg_enum) GETSTRUCT(nbr);
+ 
+ 		/* 
+ 		 * If BEFORE was specified, the new label goes in the neighbour's
+ 		 * position. Otherwise, it goes in the position after that.
+ 		 */
+ 		newelemorder = nbr_en->enumsortorder;
+ 		if (newValIsAfter)
+ 			newelemorder++;
+ 
+ 		/* 
+ 		 * Add 1 to the sortorder of all the labels after where the
+ 		 * new label goes. Do it from the end back so we don't get
+ 		 * uniqueness violations.
+ 		 */
+ 		for (i = nelems - 1; i>= 0; i--)
+ 		{
+ 			HeapTuple newtup;
+ 			Form_pg_enum exst_en = (Form_pg_enum) GETSTRUCT(existing[i]);
+ 			if (exst_en->enumsortorder < newelemorder)
+ 				break;
+ 			
+ 			newtup = heap_copytuple(existing[i]);
+ 			exst_en = (Form_pg_enum) GETSTRUCT(newtup);
+ 			exst_en->enumsortorder ++;
+ 
+ 			simple_heap_update(pg_enum, &newtup->t_self, newtup);
+ 
+ 			CatalogUpdateIndexes(pg_enum, newtup);
+ 
+ 		}
+ 
+ 	}
+ 
+ 	if (OidIsValid(binary_upgrade_next_pg_enum_oid))
+ 	{
+ 		if (neighbour != NULL)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("BEFORE/AFTER incompatible with binary upgrade.")));
+ 
+ 		newOid = binary_upgrade_next_pg_enum_oid;
+ 		binary_upgrade_next_pg_enum_oid = InvalidOid;
+ 	}
+ 	else
+ 	{
+ 		/* 
+ 		 * Non upgrade case, we allocate a new Oid for the value. 
+ 		 * 
+ 		 * We try to give the new element an even numbered Oid if it's safe,
+ 		 * which should be true if these conditions hold:
+ 		 *   . the Oid of the highest sorted exiting value is even, and 
+ 		 *   . the new value is to sort after that value, and
+ 		 *   . there hasn't been Oid wraparound.
+ 		 * For all other cases we allocate an odd Oid.
+ 		 */
+ 		   
+ 
+ 		newOid = GetNewOid(pg_enum);
+ 		if (newelemorder > nelems)
+ 		{
+ 			Oid last_elem_oid = InvalidOid;
+ 			int i;
+ 
+ 			for (i = nelems - 1; i>= 0; i--)
+ 			{
+ 				Form_pg_enum last_en;
+ 				last_en = (Form_pg_enum) GETSTRUCT(&(list->members[i]->tuple));
+ 				if (last_en->enumsortorder == nelems)
+ 				{
+ 					last_elem_oid = HeapTupleGetOid(&(list->members[i]->tuple));
+ 					break;
+ 				}
+ 			}
+ 
+ 			if (last_elem_oid %2 == 0)
+ 				while ((newOid %2 == 1 && newOid > last_elem_oid) ||
+ 					   (newOid %2 == 0 && newOid < last_elem_oid))
+ 					newOid = GetNewOid(pg_enum);
+ 			else
+ 				while (newOid %2 == 0)
+ 					newOid = GetNewOid(pg_enum);
+ 		}
+ 		else
+ 		{
+ 			while (newOid %2 == 0)
+ 				newOid = GetNewOid(pg_enum);
+ 		}
+ 	}
+ 
+ 	/* set up the new entry */
+ 	tupDesc = pg_enum->rd_att;
+ 	memset(nulls, false, sizeof(nulls));
+ 	values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
+ 	namestrcpy(&enumlabel, newVal);
+ 	values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
+ 	values[Anum_pg_enum_enumsortorder -1] =	Int32GetDatum(newelemorder);
+ 	enum_tup = heap_form_tuple(tupDesc, 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);
+ 
+ 	ReleaseCatCacheList(list);
+ 
+ }
  
  
  /*
***************
*** 33,40 **** 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)
  {
  	Relation	pg_enum;
  	TupleDesc	tupDesc;
--- 230,236 ----
   * vals is a list of Value strings.
   */
  void
! EnumValuesCreate(Oid enumTypeOid, List *vals)
  {
  	Relation	pg_enum;
  	TupleDesc	tupDesc;
***************
*** 50,58 **** EnumValuesCreate(Oid enumTypeOid, List *vals,
  	num_elems = list_length(vals);
  
  	/*
! 	 * XXX 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?
  	 */
  
  	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
--- 246,254 ----
  	num_elems = list_length(vals);
  
  	/*
! 	 * 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.
! 	 * It is probably not worth trying harder.
  	 */
  
  	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
***************
*** 62,96 **** EnumValuesCreate(Oid enumTypeOid, List *vals,
  	 * Allocate oids
  	 */
  	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
  	{
  		/*
! 		 * 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.
  		 */
! 		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);
  	}
  
  	/* and make the entries */
  	memset(nulls, false, sizeof(nulls));
--- 258,287 ----
  	 * Allocate oids
  	 */
  	oids = (Oid *) palloc(num_elems * sizeof(Oid));
! 
! 	/*
! 	 * 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.
! 	 */
! 	for (elemno = 0; elemno < num_elems; elemno++)
  	{
  		/*
! 		 * The pg_enum.oid is stored in user tables.  This oid must be
! 		 * preserved by binary upgrades.
! 		 *
! 		 * We allocate all new enums as evenly numbered Oids
! 		 * so they will sort fast.
  		 */
! 		Oid new_oid;
! 		while ((new_oid = GetNewOid(pg_enum)) %2 == 1)
! 			;
! 		oids[elemno] = new_oid;
  	}
+ 	/* sort them, just in case counter wrapped from high to low */
+ 	qsort(oids, num_elems, sizeof(Oid), oid_cmp);
  
  	/* and make the entries */
  	memset(nulls, false, sizeof(nulls));
***************
*** 114,119 **** EnumValuesCreate(Oid enumTypeOid, List *vals,
--- 305,311 ----
  		values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
  		namestrcpy(&enumlabel, lab);
  		values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
+ 		values[Anum_pg_enum_enumsortorder -1] = Int32GetDatum(elemno+1);
  
  		tup = heap_form_tuple(tupDesc, values, nulls);
  		HeapTupleSetOid(tup, oids[elemno]);
***************
*** 164,170 **** EnumValuesDelete(Oid enumTypeOid)
  }
  
  
! /* qsort comparison function */
  static int
  oid_cmp(const void *p1, const void *p2)
  {
--- 356,362 ----
  }
  
  
! /* qsort comparison for oids */
  static int
  oid_cmp(const void *p1, const void *p2)
  {
***************
*** 177,179 **** oid_cmp(const void *p1, const void *p2)
--- 369,384 ----
  		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);
+ 
+ 	return en1->enumsortorder - en2->enumsortorder;
+ }
+ 
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
***************
*** 85,96 **** 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 char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
  					Oid baseTypeOid,
  					int typMod, Constraint *constr,
  					char *domainName);
- 
- 
  /*
   * DefineType
   *		Registers a new base type.
--- 85,95 ----
  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 checkEnumOwner(HeapTuple tup, TypeName *typename);
  static char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
  					Oid baseTypeOid,
  					int typMod, Constraint *constr,
  					char *domainName);
  /*
   * DefineType
   *		Registers a new base type.
***************
*** 563,569 **** DefineType(List *names, List *parameters)
  				   -1,			/* typMod (Domains only) */
  				   0,			/* Array Dimensions of typbasetype */
  				   false);		/* Type NOT NULL */
- 
  	/*
  	 * Create the array type that goes with it.
  	 */
--- 562,567 ----
***************
*** 1044,1050 **** DefineDomain(CreateDomainStmt *stmt)
  				   storage,		/* TOAST strategy */
  				   basetypeMod, /* typeMod value */
  				   typNDims,	/* Array dimensions for base type */
! 				   typNotNull); /* Type NOT NULL */
  
  	/*
  	 * Process constraints which refer to the domain ID returned by TypeCreate
--- 1042,1048 ----
  				   storage,		/* TOAST strategy */
  				   basetypeMod, /* typeMod value */
  				   typNDims,	/* Array dimensions for base type */
! 				   typNotNull);  /* Type NOT NULL */
  
  	/*
  	 * Process constraints which refer to the domain ID returned by TypeCreate
***************
*** 1094,1099 **** DefineEnum(CreateEnumStmt *stmt)
--- 1092,1100 ----
  	AclResult	aclresult;
  	Oid			old_type_oid;
  	Oid			enumArrayOid;
+ 	int         num_labels;
+ 
+ 	num_labels = list_length(stmt->vals);
  
  	/* Convert list of names to a name and namespace */
  	enumNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
***************
*** 1156,1162 **** DefineEnum(CreateEnumStmt *stmt)
  				   false);		/* Type NOT NULL */
  
  	/* Enter the enum's values into pg_enum */
! 	EnumValuesCreate(enumTypeOid, stmt->vals, InvalidOid);
  
  	/*
  	 * Create the array type that goes with it.
--- 1157,1163 ----
  				   false);		/* Type NOT NULL */
  
  	/* Enter the enum's values into pg_enum */
! 	EnumValuesCreate(enumTypeOid, stmt->vals);
  
  	/*
  	 * Create the array type that goes with it.
***************
*** 1197,1202 **** DefineEnum(CreateEnumStmt *stmt)
--- 1198,1259 ----
  	pfree(enumArrayName);
  }
  
+ /*
+  * AlterEnum
+  *		Registers a new label for an existing enum.
+  */
+ void
+ AlterEnum (AlterEnumStmt *stmt)
+ {
+ 	Oid			enum_type_oid;
+ 	TypeName  *typename;
+ 	HeapTuple tup;
+ 	Form_pg_type typTup;
+ 
+ 	/* 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);
+ 
+ 	typTup = (Form_pg_type) GETSTRUCT(tup);
+ 
+ 	/* Check it's an enum and check user has permission to ALTER the enum */
+ 	checkEnumOwner(tup, typename);
+ 
+ 	/* Add the new label */
+ 	AddEnumLabel (enum_type_oid, stmt->newVal, 
+ 				  stmt->newValNeighbour, 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, TypeName *typename)
+ {
+ 	Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup);
+ 
+ 	/* Check that this is actually a domain */
+ 	if (typTup->typtype != TYPTYPE_ENUM)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 				 errmsg("\"%s\" is not an enum",
+ 						TypeNameToString(typename))));
+ 
+ 	/* 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.
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2901,2906 **** _copyCreateEnumStmt(CreateEnumStmt *from)
--- 2901,2919 ----
  	return newnode;
  }
  
+ static AlterEnumStmt *
+ _copyAlterEnumStmt(AlterEnumStmt *from)
+ {
+ 	AlterEnumStmt *newnode = makeNode(AlterEnumStmt);
+ 
+ 	COPY_NODE_FIELD(typeName);
+ 	COPY_STRING_FIELD(newVal);
+ 	COPY_STRING_FIELD(newValNeighbour);
+ 	COPY_SCALAR_FIELD(newValIsAfter);
+ 
+ 	return newnode;
+ }
+ 
  static ViewStmt *
  _copyViewStmt(ViewStmt *from)
  {
***************
*** 4064,4069 **** copyObject(void *from)
--- 4077,4085 ----
  		case T_CreateEnumStmt:
  			retval = _copyCreateEnumStmt(from);
  			break;
+ 		case T_AlterEnumStmt:
+ 			retval = _copyAlterEnumStmt(from);
+ 			break;
  		case T_ViewStmt:
  			retval = _copyViewStmt(from);
  			break;
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 1393,1398 **** _equalCreateEnumStmt(CreateEnumStmt *a, CreateEnumStmt *b)
--- 1393,1409 ----
  }
  
  static bool
+ _equalAlterEnumStmt(AlterEnumStmt *a, AlterEnumStmt *b)
+ {
+ 	COMPARE_NODE_FIELD(typeName);
+ 	COMPARE_STRING_FIELD(newVal);
+ 	COMPARE_STRING_FIELD(newValNeighbour);
+ 	COMPARE_SCALAR_FIELD(newValIsAfter);
+ 
+ 	return true;
+ }
+ 
+ static bool
  _equalViewStmt(ViewStmt *a, ViewStmt *b)
  {
  	COMPARE_NODE_FIELD(view);
***************
*** 2700,2705 **** equal(void *a, void *b)
--- 2711,2719 ----
  		case T_CreateEnumStmt:
  			retval = _equalCreateEnumStmt(a, b);
  			break;
+ 		case T_AlterEnumStmt:
+ 			retval = _equalAlterEnumStmt(a, b);
+ 			break;
  		case T_ViewStmt:
  			retval = _equalViewStmt(a, b);
  			break;
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 182,189 **** static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
  }
  
  %type <node>	stmt schema_stmt
! 		AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterFdwStmt
! 		AlterForeignServerStmt AlterGroupStmt
  		AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
  		AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
  		AlterRoleStmt AlterRoleSetStmt
--- 182,189 ----
  }
  
  %type <node>	stmt schema_stmt
!         AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
!         AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
  		AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
  		AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
  		AlterRoleStmt AlterRoleSetStmt
***************
*** 652,657 **** stmt :
--- 652,658 ----
  			| AlterDatabaseSetStmt
  			| AlterDefaultPrivilegesStmt
  			| AlterDomainStmt
+ 			| AlterEnumStmt
  			| AlterFdwStmt
  			| AlterForeignServerStmt
  			| AlterFunctionStmt
***************
*** 3862,3867 **** enum_val_list:	Sconst
--- 3863,3908 ----
  				{ $$ = 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->newValNeighbour = 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->newValNeighbour = $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->newValNeighbour = $7;
+ 				 n->newValIsAfter = true;
+ 
+ 				 $$ = (Node *) n;
+ 			 }
+ 		 ;
+ 
+ 
  
  /*****************************************************************************
   *
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 190,195 **** check_xact_readonly(Node *parsetree)
--- 190,196 ----
  		case T_CreateTrigStmt:
  		case T_CompositeTypeStmt:
  		case T_CreateEnumStmt:
+ 		case T_AlterEnumStmt:
  		case T_ViewStmt:
  		case T_DropCastStmt:
  		case T_DropStmt:
***************
*** 860,865 **** standard_ProcessUtility(Node *parsetree,
--- 861,870 ----
  			DefineEnum((CreateEnumStmt *) parsetree);
  			break;
  
+ 		case T_AlterEnumStmt:	/* ALTER TYPE (enum) */
+ 			AlterEnum((AlterEnumStmt *) parsetree);
+ 			break;
+ 
  		case T_ViewStmt:		/* CREATE VIEW */
  			DefineView((ViewStmt *) parsetree, queryString);
  			break;
***************
*** 1868,1873 **** CreateCommandTag(Node *parsetree)
--- 1873,1882 ----
  			tag = "CREATE TYPE";
  			break;
  
+ 		case T_AlterEnumStmt:
+ 			tag = "ALTER TYPE";
+ 			break;
+ 
  		case T_ViewStmt:
  			tag = "CREATE VIEW";
  			break;
***************
*** 2410,2415 **** GetCommandLogLevel(Node *parsetree)
--- 2419,2428 ----
  			lev = LOGSTMT_DDL;
  			break;
  
+ 		case T_AlterEnumStmt:
+ 			lev = LOGSTMT_DDL;
+ 			break;
+ 
  		case T_ViewStmt:
  			lev = LOGSTMT_DDL;
  			break;
*** a/src/backend/utils/adt/enum.c
--- b/src/backend/utils/adt/enum.c
***************
*** 14,19 ****
--- 14,20 ----
  #include "postgres.h"
  
  #include "catalog/pg_enum.h"
+ #include "catalog/pg_type.h"
  #include "fmgr.h"
  #include "utils/array.h"
  #include "utils/builtins.h"
***************
*** 22,30 ****
  #include "libpq/pqformat.h"
  #include "miscadmin.h"
  
  
  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 */
--- 23,52 ----
  #include "libpq/pqformat.h"
  #include "miscadmin.h"
  
+ #define BITMAPSIZE 1024
+ #define BITMAPBYTES (BITMAPSIZE / 8)
+ 
+ typedef struct 
+ {
+ 	Oid      enum_oid;
+ 	int32   sort_order;
+ } enum_sort;
+ 
+ typedef struct 
+ {
+ 	Oid       enumtypoid;
+ 	Oid       bitmap_base;
+ 	char      bitmap[BITMAPBYTES];
+ 	int       label_count;
+ 	enum_sort sort_order_list[1];
+ } enum_sort_cache;
+ 	
+ 
  
  static ArrayType *enum_range_internal(Oid enumtypoid, Oid lower, Oid upper);
! static int	enum_sort_cmp(const void *left, const void *right);
! static int	enum_oid_cmp(const void *left, const void *right);
! static enum_sort_cache *initcache(Oid arg, FunctionCallInfo fcinfo);
  
  
  /* Basic I/O support */
***************
*** 155,167 **** enum_send(PG_FUNCTION_ARGS)
  
  /* Comparison functions and related */
  
  Datum
  enum_lt(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a < b);
  }
  
  Datum
--- 177,366 ----
  
  /* Comparison functions and related */
  
+ static enum_sort_cache *
+ initcache(Oid arg, FunctionCallInfo fcinfo)
+ {
+ 	HeapTuple	enum_tup;
+ 	Form_pg_enum en;
+ 	Oid typeoid;
+ 	enum_sort_cache *mycache;
+ 	CatCList *list;
+ 	int num,i, bm_start, bm_len, start_pos, list_end;
+ 
+ 	/* free up anything we've used before) */
+ 	if (fcinfo->flinfo->fn_extra != NULL)
+ 		pfree(fcinfo->flinfo->fn_extra);
+ 
+ 	/* get the typeoid, label count and the list of values */
+ 	enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg));
+ 	en = (Form_pg_enum) GETSTRUCT(enum_tup);
+ 	typeoid = en->enumtypid;
+ 	ReleaseSysCache(enum_tup);
+ 	list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(typeoid));
+ 	num = list->n_members;
+ 	fcinfo->flinfo->fn_extra = 
+ 		MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ 						   sizeof(enum_sort_cache) + 
+ 						   (num * sizeof(enum_sort)));
+ 	mycache = (enum_sort_cache *) fcinfo->flinfo->fn_extra;
+ 	mycache->enumtypoid = typeoid;
+ 	mycache->label_count = num;
+ 	memset(mycache->bitmap, 0, BITMAPBYTES);
+ 
+ 	/* set up the list sorted by Oid */
+ 	for (i = 0; i < num; i++)
+ 	{
+ 		HeapTuple tup = &(list->members[i]->tuple);
+ 		Form_pg_enum list_en = (Form_pg_enum) GETSTRUCT(tup);
+ 
+ 		mycache->sort_order_list[i].sort_order = list_en->enumsortorder;
+ 		mycache->sort_order_list[i].enum_oid = HeapTupleGetOid(tup);
+ 	}
+ 
+ 	ReleaseCatCacheList(list);
+ 
+ 	qsort(mycache->sort_order_list,mycache->label_count,
+ 		  sizeof(enum_sort),enum_oid_cmp);
+ 
+ 
+ 	/* look for the longest suitable range  for the bitmap */
+ 	bm_len = 0;
+ 	bm_start = 0;
+ 	
+     for (start_pos = 0; start_pos < num -1; start_pos ++)
+ 	{
+ 		for (list_end = start_pos+1; list_end < num; list_end++)
+ 			if (mycache->sort_order_list[list_end].sort_order <
+ 				mycache->sort_order_list[list_end - 1].sort_order ||
+ 				mycache->sort_order_list[list_end].enum_oid -
+ 				mycache->sort_order_list[list_end].enum_oid >= BITMAPSIZE)
+ 				break;
+ 		if (list_end - start_pos > bm_len)
+ 		{
+ 			bm_len = list_end - start_pos;
+ 			bm_start = start_pos;
+ 		}
+ 		if (bm_len == num)
+ 			break;
+ 	}
+ 
+ 	/* if we found a suitable range, for the bitmap, set it up */
+ 	if (bm_len > 1)
+ 	{
+ 		int base = mycache->sort_order_list[bm_start].enum_oid;
+ 		for (i = 0; i < bm_len; i++)
+ 		{
+ 			Oid enoid = mycache->sort_order_list[i].enum_oid;
+ 			int offset = enoid - base;
+ 			int bytenum = offset / 8;
+ 			int bitmask = 1 << (offset % 8);
+ 			mycache->bitmap[bytenum] |= bitmask;
+ 		}
+ 	}
+ 	
+ 	return mycache;
+ }
+ 
+ /* fast lookup for Oids known to be in order */
+ 
+ static inline bool
+ bitmap_lookup(enum_sort_cache *mycache, Oid arg1, Oid arg2)
+ {
+ 	int offset1 = arg1 - mycache->bitmap_base;
+ 	int offset2 = arg2 - mycache->bitmap_base;
+ 	int bytenum, bitmask;
+ 	if (offset1 < 0 || offset2 < 0 || 
+ 		offset1 > BITMAPSIZE || offset2 > BITMAPSIZE)
+ 		return false;
+ 	bytenum = offset1 / 8;
+ 	bitmask = 1 << (offset1 % 8);
+ 	if ((mycache->bitmap[bytenum] & bitmask) == 0)
+ 		return false;
+ 	bytenum = offset2 / 8;
+ 	bitmask = 1 << (offset2 % 8);
+ 	if ((mycache->bitmap[bytenum] & bitmask) == 0)
+ 		return false;
+ 	return true;
+ }
+ 
+ /* slower lookup for Oids not known to be in order */
+ 
+ static inline int
+ find_sortorder(enum_sort_cache *mycache, Oid arg)
+ {
+ 	enum_sort *es;
+ 	enum_sort srch;
+ 
+ 	srch.enum_oid = arg;
+ 	es = bsearch(&srch,
+ 				 mycache->sort_order_list,
+ 				 mycache->label_count,
+ 				 sizeof(enum_sort),
+ 				 enum_oid_cmp);
+ 	if (es == NULL)
+ 		return -1;
+ 	
+ 	return es->sort_order;
+ 
+ }
+ 
+ /* enum_ccmp is the common engine for all the visible comparison functions */
+ 
+ static inline int 
+ enum_ccmp(Oid arg1, Oid arg2, FunctionCallInfo fcinfo)
+ {
+ 
+ 	enum_sort_cache *mycache;
+ 	int sort_1, sort_2;
+ 
+ 	/* evenly numbered Oids are know to sort right */
+ 	if (arg1 % 2 == 0 && arg2 % 2 == 0)
+ 		return arg1 - arg2;
+ 
+ 
+ 	/* so are oids that are equal */
+ 	if (arg1 == arg2)
+ 		return 0;
+ 
+ 	/* get the cached info, set it up if absent */
+ 	mycache = (enum_sort_cache *) fcinfo->flinfo->fn_extra;
+ 	if (mycache == NULL )
+ 		mycache = initcache(arg1, fcinfo);
+ 
+ 	/* first try the fast lookup */
+ 	if (bitmap_lookup(mycache, arg1, arg2))			
+ 		return arg1 - arg2;
+ 	
+ 	/* try the slow lookup  */
+ 	sort_1 = find_sortorder(mycache,arg1);
+ 	sort_2 = find_sortorder(mycache,arg2);
+ 
+ 	if (sort_1 <= 0 || sort_2 <= 0)
+ 	{
+ 		/* 
+ 		 * We couldn't find one or both values.
+ 		 * That means the enum has changed under us, so
+ 		 * re-initialize the cache and try again.
+ 		 */
+ 		mycache = initcache(arg1, fcinfo);
+ 		sort_1 = find_sortorder(mycache,arg1);
+ 		sort_2 = find_sortorder(mycache,arg2);
+ 		/* if we fail the secind time around, give up */
+ 		if (sort_1 <= 0 || sort_2 <= 0)
+ 			elog(ERROR, "values missing for enum %s",
+ 			 format_type_be(mycache->enumtypoid));
+ 	}
+ 
+ 	return sort_1 - sort_2;
+ }
+ 
  Datum
  enum_lt(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) < 0);
  }
  
  Datum
***************
*** 170,176 **** enum_le(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a <= b);
  }
  
  Datum
--- 369,375 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) <= 0);
  }
  
  Datum
***************
*** 197,203 **** enum_ge(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a >= b);
  }
  
  Datum
--- 396,402 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) >= 0);
  }
  
  Datum
***************
*** 206,212 **** enum_gt(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a > b);
  }
  
  Datum
--- 405,411 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) > 0);
  }
  
  Datum
***************
*** 215,221 **** enum_smaller(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(a <= b ? a : b);
  }
  
  Datum
--- 414,420 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(enum_ccmp(a,b,fcinfo) < 0 ? a : b);
  }
  
  Datum
***************
*** 224,230 **** enum_larger(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(a >= b ? a : b);
  }
  
  Datum
--- 423,429 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(enum_ccmp(a,b,fcinfo) > 0 ? a : b);
  }
  
  Datum
***************
*** 233,242 **** 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)
  		PG_RETURN_INT32(0);
  	else
  		PG_RETURN_INT32(-1);
  }
--- 432,441 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	if (a == b)
  		PG_RETURN_INT32(0);
+ 	else if (enum_ccmp(a,b,fcinfo) > 0)
+ 		PG_RETURN_INT32(1);
  	else
  		PG_RETURN_INT32(-1);
  }
***************
*** 248,253 **** enum_first(PG_FUNCTION_ARGS)
--- 447,453 ----
  {
  	Oid			enumtypoid;
  	Oid			min = InvalidOid;
+ 	int         min_sort = -1; /* value will never in fact be used */
  	CatCList   *list;
  	int			num,
  				i;
***************
*** 267,276 **** enum_first(PG_FUNCTION_ARGS)
  	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);
--- 467,480 ----
  	num = list->n_members;
  	for (i = 0; i < num; i++)
  	{
! 		HeapTuple tup = &(list->members[i]->tuple);
! 		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
! 
! 		if (!OidIsValid(min) || en->enumsortorder < min_sort)
! 		{
! 			min = HeapTupleGetOid(tup);
! 			min_sort = en->enumsortorder;
! 		}
  	}
  
  	ReleaseCatCacheList(list);
***************
*** 287,292 **** enum_last(PG_FUNCTION_ARGS)
--- 491,497 ----
  {
  	Oid			enumtypoid;
  	Oid			max = InvalidOid;
+ 	int         max_sort = -1; /* value will never in fact be used */
  	CatCList   *list;
  	int			num,
  				i;
***************
*** 306,315 **** enum_last(PG_FUNCTION_ARGS)
  	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);
--- 511,524 ----
  	num = list->n_members;
  	for (i = 0; i < num; i++)
  	{
! 		HeapTuple tup = &(list->members[i]->tuple);
! 		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
! 
! 		if (!OidIsValid(max) || en->enumsortorder > max_sort)
! 		{
! 			max = HeapTupleGetOid(tup);
! 			max_sort = en->enumsortorder;
! 		}
  	}
  
  	ReleaseCatCacheList(list);
***************
*** 382,427 **** enum_range_internal(Oid enumtypoid, Oid lower, Oid upper)
  				i,
  				j;
  	Datum	   *elems;
  
  	list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid));
  	total = list->n_members;
  
  	elems = (Datum *) palloc(total * sizeof(Datum));
  
- 	j = 0;
  	for (i = 0; i < total; i++)
  	{
! 		Oid			val = HeapTupleGetOid(&(list->members[i]->tuple));
  
- 		if ((!OidIsValid(lower) || lower <= val) &&
- 			(!OidIsValid(upper) || val <= upper))
- 			elems[j++] = ObjectIdGetDatum(val);
  	}
  
  	/* shouldn't need the cache anymore */
  	ReleaseCatCacheList(list);
  
! 	/* sort results into OID order */
! 	qsort(elems, j, sizeof(Datum), enum_elem_cmp);
  
  	/* note this hardwires some details about the representation of Oid */
  	result = construct_array(elems, j, 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;
  }
--- 591,666 ----
  				i,
  				j;
  	Datum	   *elems;
+ 	enum_sort  *sort_items;
+ 	bool        left_found;
  
  	list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid));
  	total = list->n_members;
  
  	elems = (Datum *) palloc(total * sizeof(Datum));
+ 	sort_items = (enum_sort *) palloc(total * sizeof(enum_sort));
  
  	for (i = 0; i < total; i++)
  	{
! 		HeapTuple tup = &(list->members[i]->tuple);
! 		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
! 
! 		sort_items[i].enum_oid = HeapTupleGetOid(tup);
! 		sort_items[i].sort_order = en->enumsortorder;
  
  	}
  
  	/* shouldn't need the cache anymore */
  	ReleaseCatCacheList(list);
  
! 	/* sort results into sort_order sequence */
! 	qsort(sort_items, total, sizeof(enum_sort), enum_sort_cmp);
! 
! 	j = 0; 
! 	left_found = !OidIsValid(lower);
! 	for (i=0; i < total; i++)
! 	{
! 		if (! left_found && lower == sort_items[i].enum_oid)
! 			left_found = true;
! 
! 		if (left_found)
! 			elems[j++] = ObjectIdGetDatum(sort_items[i].enum_oid);
! 
! 		if (OidIsValid(upper) && upper == sort_items[i].enum_oid)
! 			break;
! 	}
  
  	/* note this hardwires some details about the representation of Oid */
  	result = construct_array(elems, j, enumtypoid, sizeof(Oid), true, 'i');
  
  	pfree(elems);
+ 	pfree(sort_items);
  
  	return result;
  }
  
! /* 
!  * qsort comparison using sort order, for range routines 
!  */
  static int
! enum_sort_cmp(const void *left, const void *right)
  {
! 	enum_sort  *l = (enum_sort *) left;
! 	enum_sort  *r = (enum_sort *) right;
  
! 	return l->sort_order - r->sort_order;
! }
! 
! /* 
!  * qsort comparison using OID order for comparison search cache
!  */
! static int 
! enum_oid_cmp(const void *es1, const void *es2)
! {
! 	enum_sort *p1, *p2;
! 	p1 = (enum_sort *)es1;
! 	p2 = (enum_sort *)es2;
! 	return p1->enum_oid - p2->enum_oid;
  }
+ 
+ 
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 6653,6669 **** dumpEnumType(Archive *fout, TypeInfo *tyinfo)
  	PQExpBuffer query = createPQExpBuffer();
  	PGresult   *res;
  	int			num,
! 				i;
  	Oid			enum_oid;
  	char	   *label;
  
  	/* Set proper schema search path so regproc references list correctly */
  	selectSourceSchema(tyinfo->dobj.namespace->dobj.name);
  
! 	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);
--- 6653,6676 ----
  	PQExpBuffer query = createPQExpBuffer();
  	PGresult   *res;
  	int			num,
! 		        i;
  	Oid			enum_oid;
  	char	   *label;
  
  	/* Set proper schema search path so regproc references list correctly */
  	selectSourceSchema(tyinfo->dobj.namespace->dobj.name);
  
!     if  (fout->remoteVersion > 90000)
! 		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);
***************
*** 6709,6725 **** dumpEnumType(Archive *fout, TypeInfo *tyinfo)
  		{
  			enum_oid = atooid(PQgetvalue(res, i, PQfnumber(res, "oid")));
  			label = PQgetvalue(res, i, PQfnumber(res, "enumlabel"));
! 
  			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);
! 			appendStringLiteralAH(q, label, fout);
! 			appendPQExpBuffer(q, ");\n");
  		}
- 		appendPQExpBuffer(q, "\n");
  	}
  
  	ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId,
--- 6716,6734 ----
  		{
  			enum_oid = atooid(PQgetvalue(res, i, PQfnumber(res, "oid")));
  			label = PQgetvalue(res, i, PQfnumber(res, "enumlabel"));
! 			
  			if (i == 0)
  				appendPQExpBuffer(q, "\n-- For binary upgrade, must preserve pg_enum oids\n");
  			appendPQExpBuffer(q,
! 							  "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\n");
  		}
  	}
  
  	ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId,
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
***************
*** 473,489 **** 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"
  						  "      ),\n"
  						  "      E'\\n'\n"
  						  "  ) AS \"%s\",\n",
  						  gettext_noop("Elements"));
  
  	appendPQExpBuffer(&buf,
  				"  pg_catalog.obj_description(t.oid, 'pg_type') as \"%s\"\n",
--- 473,499 ----
  						  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");
! 
! 		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",
*** a/src/include/catalog/indexing.h
--- b/src/include/catalog/indexing.h
***************
*** 147,152 **** DECLARE_UNIQUE_INDEX(pg_enum_oid_index, 3502, on pg_enum using btree(oid oid_ops
--- 147,154 ----
  #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, 3539, on pg_enum using btree(enumtypid oid_ops, enumsortorder int4_ops));
+ #define EnumTypIdSortOrderIndexId 3539
  
  /* 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));
*** a/src/include/catalog/pg_enum.h
--- b/src/include/catalog/pg_enum.h
***************
*** 35,40 **** CATALOG(pg_enum,3501)
--- 35,41 ----
  {
  	Oid			enumtypid;		/* OID of owning enum type */
  	NameData	enumlabel;		/* text representation of enum value */
+ 	int4        enumsortorder;  /* sort order for this enum label */
  } FormData_pg_enum;
  
  /* ----------------
***************
*** 48,56 **** typedef FormData_pg_enum *Form_pg_enum;
   *		compiler constants for pg_enum
   * ----------------
   */
! #define Natts_pg_enum					2
  #define Anum_pg_enum_enumtypid			1
  #define Anum_pg_enum_enumlabel			2
  
  /* ----------------
   *		pg_enum has no initial contents
--- 49,58 ----
   *		compiler constants for pg_enum
   * ----------------
   */
! #define Natts_pg_enum					3
  #define Anum_pg_enum_enumtypid			1
  #define Anum_pg_enum_enumlabel			2
+ #define Anum_pg_enum_enumsortorder		3
  
  /* ----------------
   *		pg_enum has no initial contents
***************
*** 60,67 **** 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 EnumValuesDelete(Oid enumTypeOid);
  
  #endif   /* PG_ENUM_H */
--- 62,70 ----
  /*
   * prototypes for functions in pg_enum.c
   */
! extern void EnumValuesCreate(Oid enumTypeOid, List *vals);
  extern void EnumValuesDelete(Oid enumTypeOid);
+ extern void AddEnumLabel(Oid enumTypeOid, char *newVal, char *neighbour,
+ 						 bool newValIsAfter);
  
  #endif   /* PG_ENUM_H */
*** a/src/include/catalog/pg_type_fn.h
--- b/src/include/catalog/pg_type_fn.h
***************
*** 50,56 **** extern Oid TypeCreate(Oid newTypeOid,
  		   char storage,
  		   int32 typeMod,
  		   int32 typNDims,
! 		   bool typeNotNull);
  
  extern void GenerateTypeDependencies(Oid typeNamespace,
  						 Oid typeObjectId,
--- 50,56 ----
  		   char storage,
  		   int32 typeMod,
  		   int32 typNDims,
!  		   bool typeNotNull);
  
  extern void GenerateTypeDependencies(Oid typeNamespace,
  						 Oid typeObjectId,
*** a/src/include/commands/typecmds.h
--- b/src/include/commands/typecmds.h
***************
*** 24,29 **** extern void RemoveTypes(DropStmt *drop);
--- 24,30 ----
  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);
  
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
***************
*** 351,357 **** typedef enum NodeTag
  	T_DropUserMappingStmt,
  	T_AlterTableSpaceOptionsStmt,
  	T_SecLabelStmt,
! 
  	/*
  	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
  	 */
--- 351,357 ----
  	T_DropUserMappingStmt,
  	T_AlterTableSpaceOptionsStmt,
  	T_SecLabelStmt,
! 	T_AlterEnumStmt,
  	/*
  	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
  	 */
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 2195,2200 **** typedef struct CreateEnumStmt
--- 2195,2214 ----
  
  
  /* ----------------------
+  *		Alter Type Statement, enum types
+  * ----------------------
+  */
+ typedef struct AlterEnumStmt
+ {
+ 	NodeTag		type;
+ 	List	   *typeName;		/* qualified name (list of Value strings) */
+ 	char	   *newVal;			/* new enum value */
+ 	char	   *newValNeighbour;/* neighbouring enum value */
+ 	bool	    newValIsAfter;	/* new enum value is after neighbour? */
+ } AlterEnumStmt;
+ 
+ 
+ /* ----------------------
   *		Create View Statement
   * ----------------------
   */
*** a/src/test/regress/expected/enum.out
--- b/src/test/regress/expected/enum.out
***************
*** 25,30 **** ERROR:  invalid input value for enum rainbow: "mauve"
--- 25,92 ----
  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   |             1
+  venus     |             2
+  earth     |             3
+  mars      |             4
+  jupiter   |             5
+  saturn    |             6
+  uranus    |             7
+  neptune   |             8
+ (8 rows)
+ 
+ select 'mars'::planets > 'mercury' as using_sortorder;
+  using_sortorder 
+ -----------------
+  t
+ (1 row)
+ 
+ -- 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 label.
+ DROP TYPE planets;
+ --
  -- Basic table creation, row selection
  --
  CREATE TABLE enumtest (col rainbow);
***************
*** 403,409 **** 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 
! -----------+-----------
  (0 rows)
  
--- 465,471 ----
  
  SELECT * FROM pg_enum WHERE NOT EXISTS
    (SELECT 1 FROM pg_type WHERE pg_type.oid = enumtypid);
!  enumtypid | enumlabel | enumsortorder 
! -----------+-----------+---------------
  (0 rows)
  
*** a/src/test/regress/sql/enum.sql
--- b/src/test/regress/sql/enum.sql
***************
*** 16,21 **** SELECT 'red'::rainbow;
--- 16,61 ----
  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 'mars'::planets > 'mercury' as using_sortorder;
+ 
+ -- errors for adding labels 
+ ALTER TYPE planets ADD 
+ 	  'plutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutopluto';
+ 
+ ALTER TYPE planets ADD 'pluto' AFTER 'zeus';
+ 
+ DROP TYPE planets;
+ --
  -- Basic table creation, row selection
  --
  CREATE TABLE enumtest (col rainbow);
#69Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Andrew Dunstan (#68)
Re: WIP: extensible enums

On 19 October 2010 05:21, Andrew Dunstan <andrew@dunslane.net> wrote:

On 10/18/2010 10:52 AM, Tom Lane wrote:

We could possibly deal with enum types that follow the existing
convention if we made the cache entry hold a list of all the original,
known-to-be-sorted OIDs.  (This could be reasonably compact and cheap to
probe if it were represented as a starting OID and a Bitmapset of delta
values, since we can assume that the initial set of OIDs is pretty close
together.)  But we have to have that cache entry, and we have to consult
it on every single comparison, so it's definitely going to be slower
than before.

So I'm thinking the comparison procedure goes like this:

1. Both OIDs even?
       If so, just compare them numerically, and we're done.

2. Lookup cache entry for enum type.

3. Both OIDs in list of known-sorted OIDs?
       If so, just compare them numerically, and we're done.

4. Search the part of the cache entry that lists sort positions.
       If not both present, refresh the cache entry.
       If still not present, throw error.

5. Compare by sort positions.

Step 4 is the slowest part but would be avoided in most cases.
However, step 2 is none too speedy either, and would usually
be required when dealing with pre-existing enums.

OK, I've made adjustments that I think do what you're suggesting.

Patch is attached.

Ah, I'd missed the point about the bitmapset. In the most common case,
most of the enum elements are probably going to be in the right order,
so you save a lot by identifying that case quickly.

I didn't have time to play with hash maps myself, but I don't think it
will make much difference now because hopefully the binary search
isn't going to be hit a lot anyway.

There are a couple of things that look odd about this code though (I
haven't tested it, but they look wrong):

In the loop identifying the longest range:

for (list_end = start_pos+1; list_end < num; list_end++)
if (mycache->sort_order_list[list_end].sort_order <
mycache->sort_order_list[list_end - 1].sort_order ||
mycache->sort_order_list[list_end].enum_oid -
mycache->sort_order_list[list_end].enum_oid >= BITMAPSIZE)

That last test isn't doing anything. Shouldn't the second "list_end"
should be "start_pos"?

In the loop that sets bits in the bitmap, it looks like it's assuming
the range starts at 0. I haven't had any caffeine yet, so maybe I'm
misunderstanding it, but I can't see anywhere that sets bitmap_base.

Regards,
Dean

Show quoted text

Alternatively this can be pulled from
<git@github.com:adunstan/postgresql-dev.git>

cheers

andrew

#70Thom Brown
thom@linux.com
In reply to: Andrew Dunstan (#68)
Re: WIP: extensible enums

On 19 October 2010 05:21, Andrew Dunstan <andrew@dunslane.net> wrote:

On 10/18/2010 10:52 AM, Tom Lane wrote:

We could possibly deal with enum types that follow the existing
convention if we made the cache entry hold a list of all the original,
known-to-be-sorted OIDs.  (This could be reasonably compact and cheap to
probe if it were represented as a starting OID and a Bitmapset of delta
values, since we can assume that the initial set of OIDs is pretty close
together.)  But we have to have that cache entry, and we have to consult
it on every single comparison, so it's definitely going to be slower
than before.

So I'm thinking the comparison procedure goes like this:

1. Both OIDs even?
       If so, just compare them numerically, and we're done.

2. Lookup cache entry for enum type.

3. Both OIDs in list of known-sorted OIDs?
       If so, just compare them numerically, and we're done.

4. Search the part of the cache entry that lists sort positions.
       If not both present, refresh the cache entry.
       If still not present, throw error.

5. Compare by sort positions.

Step 4 is the slowest part but would be avoided in most cases.
However, step 2 is none too speedy either, and would usually
be required when dealing with pre-existing enums.

OK, I've made adjustments that I think do what you're suggesting.

Patch is attached.

Alternatively this can be pulled from
<git@github.com:adunstan/postgresql-dev.git>

Andrew, can't you get your own repo at git.postgresql.org?

--
Thom Brown
Twitter: @darkixion
IRC (freenode): dark_ixion
Registered Linux user: #516935

#71Andrew Dunstan
andrew@dunslane.net
In reply to: Dean Rasheed (#69)
1 attachment(s)
Re: WIP: extensible enums

On 10/19/2010 04:00 AM, Dean Rasheed wrote:

There are a couple of things that look odd about this code though (I
haven't tested it, but they look wrong):

You're right, thanks. I have fixed those. New patch attached.

cheers

andrew

Attachments:

venum7.patchtext/x-patch; name=venum7.patchDownload
*** a/contrib/pg_upgrade/function.c
--- b/contrib/pg_upgrade/function.c
***************
*** 61,85 **** install_support_functions(migratorContext *ctx)
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 				"		binary_upgrade.set_next_heap_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_toast_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_index_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			 "		binary_upgrade.add_pg_enum_label(OID, OID, NAME) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
--- 61,85 ----
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 					 "		binary_upgrade.set_next_pg_enum_oid(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 				"		binary_upgrade.set_next_heap_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_toast_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_index_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
*** a/contrib/pg_upgrade_support/pg_upgrade_support.c
--- b/contrib/pg_upgrade_support/pg_upgrade_support.c
***************
*** 30,35 **** PG_MODULE_MAGIC;
--- 30,36 ----
  extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
  extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_array_oid;
  extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_toast_oid;
+ extern PGDLLIMPORT Oid binary_upgrade_next_pg_enum_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;
***************
*** 37,54 **** extern PGDLLIMPORT Oid binary_upgrade_next_index_relfilenode;
  Datum		set_next_pg_type_oid(PG_FUNCTION_ARGS);
  Datum		set_next_pg_type_array_oid(PG_FUNCTION_ARGS);
  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);
  
  PG_FUNCTION_INFO_V1(set_next_pg_type_oid);
  PG_FUNCTION_INFO_V1(set_next_pg_type_array_oid);
  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);
  
  Datum
  set_next_pg_type_oid(PG_FUNCTION_ARGS)
--- 38,55 ----
  Datum		set_next_pg_type_oid(PG_FUNCTION_ARGS);
  Datum		set_next_pg_type_array_oid(PG_FUNCTION_ARGS);
  Datum		set_next_pg_type_toast_oid(PG_FUNCTION_ARGS);
+ Datum       set_next_pg_enum_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);
  
  PG_FUNCTION_INFO_V1(set_next_pg_type_oid);
  PG_FUNCTION_INFO_V1(set_next_pg_type_array_oid);
  PG_FUNCTION_INFO_V1(set_next_pg_type_toast_oid);
+ PG_FUNCTION_INFO_V1(set_next_pg_enum_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);
  
  Datum
  set_next_pg_type_oid(PG_FUNCTION_ARGS)
***************
*** 81,86 **** set_next_pg_type_toast_oid(PG_FUNCTION_ARGS)
--- 82,97 ----
  }
  
  Datum
+ set_next_pg_enum_oid(PG_FUNCTION_ARGS)
+ {
+ 	Oid			enumoid = PG_GETARG_OID(0);
+ 
+ 	binary_upgrade_next_pg_enum_oid = enumoid;
+ 
+ 	PG_RETURN_VOID();
+ }
+ 
+ Datum
  set_next_heap_relfilenode(PG_FUNCTION_ARGS)
  {
  	Oid			relfilenode = PG_GETARG_OID(0);
***************
*** 110,124 **** set_next_index_relfilenode(PG_FUNCTION_ARGS)
  	PG_RETURN_VOID();
  }
  
- Datum
- add_pg_enum_label(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);
- 
- 	PG_RETURN_VOID();
- }
--- 121,123 ----
*** a/doc/src/sgml/ref/alter_type.sgml
--- b/doc/src/sgml/ref/alter_type.sgml
***************
*** 23,33 **** 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> 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>
  
  <phrase>where <replaceable class="PARAMETER">action</replaceable> is one of:</phrase>
  
--- 23,34 ----
  
   <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> 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,108 **** ALTER TYPE <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <replace
--- 104,131 ----
       </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 sort order is not set using <literal>BEFORE</literal> or 
+ 	  <literal>AFTER</literal>, then the new item is placed at the end of
+ 	  the list of values.
+      </para>
+ 	 <note>
+ 	  <para>
+ 	   Adding a new enum value will in some cases lower the comparison and 
+ 	   sorting performance of the enum type. This will only usually occur if  
+ 	   <literal>BEFORE</literal> or <literal>AFTER</literal> are used to set 
+ 	   the new value's sort order position somewhere other than at the end 
+ 	   of the list. Optimal performance will be restored if the database is 
+ 	   dumped and reloaded.
+ 	  </para>
+ 	 </note>
+     </listitem>
+    </varlistentry>
    </variablelist>
    </para>
  
***************
*** 196,201 **** ALTER TYPE <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <replace
--- 219,254 ----
        </listitem>
       </varlistentry>
  
+      <varlistentry>
+       <term><replaceable class="PARAMETER">data_type</replaceable></term>
+       <listitem>
+        <para>
+         The data type of the attribute to add, or the new type of the
+         attribute to alter.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
+      <varlistentry>
+       <term><replaceable class="PARAMETER">new_enum_value</replaceable></term>
+       <listitem>
+        <para>
+         The new value to be added to the num type's list of values. Like all
+ 		enum literals it needs to be quoted.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
+      <varlistentry>
+       <term><replaceable class="PARAMETER">exisiting_enum_value</replaceable></term>
+       <listitem>
+        <para>
+         The neighbour of the new value to be added to the num type's list of values. Like all
+ 		enum literals it needs to be quoted.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
      </variablelist>
     </para>
    </refsect1>
***************
*** 232,237 **** ALTER TYPE email SET SCHEMA customers;
--- 285,297 ----
  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>
  
   <refsect1>
*** a/src/backend/catalog/pg_enum.c
--- b/src/backend/catalog/pg_enum.c
***************
*** 18,29 ****
--- 18,226 ----
  #include "catalog/catalog.h"
  #include "catalog/indexing.h"
  #include "catalog/pg_enum.h"
+ #include "catalog/pg_type.h"
+ #include "utils/lsyscache.h"
+ #include "utils/syscache.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
  #include "utils/rel.h"
  #include "utils/tqual.h"
  
  static int	oid_cmp(const void *p1, const void *p2);
+ static int	sort_order_cmp(const void *p1, const void *p2);
+ 
+ Oid      binary_upgrade_next_pg_enum_oid = InvalidOid;
+ 
+ /*
+  * 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, 
+ 			 char *newVal, 
+ 			 char *neighbour, 
+ 			 bool newValIsAfter)
+ {
+ 	Oid        newOid;
+ 	Relation   pg_enum;
+ 	TupleDesc	tupDesc;
+ 	Datum		values[Natts_pg_enum];
+ 	bool		nulls[Natts_pg_enum];
+ 	NameData	enumlabel;
+ 	HeapTuple   enum_tup;
+ 	int         newelemorder;
+ 	CatCList   *list;
+ 	int nelems;
+ 
+ 	/* 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)));
+ 
+ 	/* get a new OID for the label */
+ 	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
+ 
+ 	list = SearchSysCacheList1(ENUMTYPOIDNAME, 
+ 							   ObjectIdGetDatum(enumTypeOid));
+ 	nelems =  list->n_members;
+ 
+ 	if (neighbour == NULL)
+ 	{
+ 		/* 
+ 		 * Put the new label at the end of the list.
+ 		 * No change to existing tuples is required.
+ 		 */
+ 		newelemorder = nelems + 1;
+ 	}
+ 	else 
+ 	{
+ 		/* BEFORE or AFTER specified */
+ 		int			i;
+ 		HeapTuple    *existing;
+ 		HeapTuple     nbr = NULL;
+ 		Form_pg_enum nbr_en;
+ 
+ 		/* sort the list of the existing elements by enumsortorder */
+ 		existing = palloc(nelems * sizeof(HeapTuple));
+ 
+ 		for (i = 0; i < nelems; i++)
+ 			existing[i] = &(list->members[i]->tuple);
+ 
+ 		qsort(existing, nelems, sizeof(HeapTuple), sort_order_cmp);
+ 		
+ 		/* locate the neighbour element */
+ 		for (i = 0; i < nelems; i++)
+ 		{
+ 			Form_pg_enum exists_en;
+ 			exists_en = (Form_pg_enum) GETSTRUCT(existing[i]);
+ 			if (strcmp(NameStr(exists_en->enumlabel),neighbour) == 0)
+ 				nbr = existing[i];
+ 
+ 		}
+ 
+ 		if (nbr == NULL)
+ 		{
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("\"%s\" is not an existing label.", neighbour)));
+ 		}
+ 
+ 		nbr_en = (Form_pg_enum) GETSTRUCT(nbr);
+ 
+ 		/* 
+ 		 * If BEFORE was specified, the new label goes in the neighbour's
+ 		 * position. Otherwise, it goes in the position after that.
+ 		 */
+ 		newelemorder = nbr_en->enumsortorder;
+ 		if (newValIsAfter)
+ 			newelemorder++;
+ 
+ 		/* 
+ 		 * Add 1 to the sortorder of all the labels after where the
+ 		 * new label goes. Do it from the end back so we don't get
+ 		 * uniqueness violations.
+ 		 */
+ 		for (i = nelems - 1; i>= 0; i--)
+ 		{
+ 			HeapTuple newtup;
+ 			Form_pg_enum exst_en = (Form_pg_enum) GETSTRUCT(existing[i]);
+ 			if (exst_en->enumsortorder < newelemorder)
+ 				break;
+ 			
+ 			newtup = heap_copytuple(existing[i]);
+ 			exst_en = (Form_pg_enum) GETSTRUCT(newtup);
+ 			exst_en->enumsortorder ++;
+ 
+ 			simple_heap_update(pg_enum, &newtup->t_self, newtup);
+ 
+ 			CatalogUpdateIndexes(pg_enum, newtup);
+ 
+ 		}
+ 
+ 	}
+ 
+ 	if (OidIsValid(binary_upgrade_next_pg_enum_oid))
+ 	{
+ 		if (neighbour != NULL)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("BEFORE/AFTER incompatible with binary upgrade.")));
+ 
+ 		newOid = binary_upgrade_next_pg_enum_oid;
+ 		binary_upgrade_next_pg_enum_oid = InvalidOid;
+ 	}
+ 	else
+ 	{
+ 		/* 
+ 		 * Non upgrade case, we allocate a new Oid for the value. 
+ 		 * 
+ 		 * We try to give the new element an even numbered Oid if it's safe,
+ 		 * which should be true if these conditions hold:
+ 		 *   . the Oid of the highest sorted exiting value is even, and 
+ 		 *   . the new value is to sort after that value, and
+ 		 *   . there hasn't been Oid wraparound.
+ 		 * For all other cases we allocate an odd Oid.
+ 		 */
+ 		   
+ 
+ 		newOid = GetNewOid(pg_enum);
+ 		if (newelemorder > nelems)
+ 		{
+ 			Oid last_elem_oid = InvalidOid;
+ 			int i;
+ 
+ 			for (i = nelems - 1; i>= 0; i--)
+ 			{
+ 				Form_pg_enum last_en;
+ 				last_en = (Form_pg_enum) GETSTRUCT(&(list->members[i]->tuple));
+ 				if (last_en->enumsortorder == nelems)
+ 				{
+ 					last_elem_oid = HeapTupleGetOid(&(list->members[i]->tuple));
+ 					break;
+ 				}
+ 			}
+ 
+ 			if (last_elem_oid %2 == 0)
+ 				while ((newOid %2 == 1 && newOid > last_elem_oid) ||
+ 					   (newOid %2 == 0 && newOid < last_elem_oid))
+ 					newOid = GetNewOid(pg_enum);
+ 			else
+ 				while (newOid %2 == 0)
+ 					newOid = GetNewOid(pg_enum);
+ 		}
+ 		else
+ 		{
+ 			while (newOid %2 == 0)
+ 				newOid = GetNewOid(pg_enum);
+ 		}
+ 	}
+ 
+ 	/* set up the new entry */
+ 	tupDesc = pg_enum->rd_att;
+ 	memset(nulls, false, sizeof(nulls));
+ 	values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
+ 	namestrcpy(&enumlabel, newVal);
+ 	values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
+ 	values[Anum_pg_enum_enumsortorder -1] =	Int32GetDatum(newelemorder);
+ 	enum_tup = heap_form_tuple(tupDesc, 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);
+ 
+ 	ReleaseCatCacheList(list);
+ 
+ }
  
  
  /*
***************
*** 33,40 **** 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)
  {
  	Relation	pg_enum;
  	TupleDesc	tupDesc;
--- 230,236 ----
   * vals is a list of Value strings.
   */
  void
! EnumValuesCreate(Oid enumTypeOid, List *vals)
  {
  	Relation	pg_enum;
  	TupleDesc	tupDesc;
***************
*** 50,58 **** EnumValuesCreate(Oid enumTypeOid, List *vals,
  	num_elems = list_length(vals);
  
  	/*
! 	 * XXX 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?
  	 */
  
  	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
--- 246,254 ----
  	num_elems = list_length(vals);
  
  	/*
! 	 * 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.
! 	 * It is probably not worth trying harder.
  	 */
  
  	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
***************
*** 62,96 **** EnumValuesCreate(Oid enumTypeOid, List *vals,
  	 * Allocate oids
  	 */
  	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
  	{
  		/*
! 		 * 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.
  		 */
! 		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);
  	}
  
  	/* and make the entries */
  	memset(nulls, false, sizeof(nulls));
--- 258,287 ----
  	 * Allocate oids
  	 */
  	oids = (Oid *) palloc(num_elems * sizeof(Oid));
! 
! 	/*
! 	 * 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.
! 	 */
! 	for (elemno = 0; elemno < num_elems; elemno++)
  	{
  		/*
! 		 * The pg_enum.oid is stored in user tables.  This oid must be
! 		 * preserved by binary upgrades.
! 		 *
! 		 * We allocate all new enums as evenly numbered Oids
! 		 * so they will sort fast.
  		 */
! 		Oid new_oid;
! 		while ((new_oid = GetNewOid(pg_enum)) %2 == 1)
! 			;
! 		oids[elemno] = new_oid;
  	}
+ 	/* sort them, just in case counter wrapped from high to low */
+ 	qsort(oids, num_elems, sizeof(Oid), oid_cmp);
  
  	/* and make the entries */
  	memset(nulls, false, sizeof(nulls));
***************
*** 114,119 **** EnumValuesCreate(Oid enumTypeOid, List *vals,
--- 305,311 ----
  		values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
  		namestrcpy(&enumlabel, lab);
  		values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
+ 		values[Anum_pg_enum_enumsortorder -1] = Int32GetDatum(elemno+1);
  
  		tup = heap_form_tuple(tupDesc, values, nulls);
  		HeapTupleSetOid(tup, oids[elemno]);
***************
*** 164,170 **** EnumValuesDelete(Oid enumTypeOid)
  }
  
  
! /* qsort comparison function */
  static int
  oid_cmp(const void *p1, const void *p2)
  {
--- 356,362 ----
  }
  
  
! /* qsort comparison for oids */
  static int
  oid_cmp(const void *p1, const void *p2)
  {
***************
*** 177,179 **** oid_cmp(const void *p1, const void *p2)
--- 369,384 ----
  		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);
+ 
+ 	return en1->enumsortorder - en2->enumsortorder;
+ }
+ 
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
***************
*** 85,96 **** 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 char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
  					Oid baseTypeOid,
  					int typMod, Constraint *constr,
  					char *domainName);
- 
- 
  /*
   * DefineType
   *		Registers a new base type.
--- 85,95 ----
  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 checkEnumOwner(HeapTuple tup, TypeName *typename);
  static char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
  					Oid baseTypeOid,
  					int typMod, Constraint *constr,
  					char *domainName);
  /*
   * DefineType
   *		Registers a new base type.
***************
*** 563,569 **** DefineType(List *names, List *parameters)
  				   -1,			/* typMod (Domains only) */
  				   0,			/* Array Dimensions of typbasetype */
  				   false);		/* Type NOT NULL */
- 
  	/*
  	 * Create the array type that goes with it.
  	 */
--- 562,567 ----
***************
*** 1044,1050 **** DefineDomain(CreateDomainStmt *stmt)
  				   storage,		/* TOAST strategy */
  				   basetypeMod, /* typeMod value */
  				   typNDims,	/* Array dimensions for base type */
! 				   typNotNull); /* Type NOT NULL */
  
  	/*
  	 * Process constraints which refer to the domain ID returned by TypeCreate
--- 1042,1048 ----
  				   storage,		/* TOAST strategy */
  				   basetypeMod, /* typeMod value */
  				   typNDims,	/* Array dimensions for base type */
! 				   typNotNull);  /* Type NOT NULL */
  
  	/*
  	 * Process constraints which refer to the domain ID returned by TypeCreate
***************
*** 1094,1099 **** DefineEnum(CreateEnumStmt *stmt)
--- 1092,1100 ----
  	AclResult	aclresult;
  	Oid			old_type_oid;
  	Oid			enumArrayOid;
+ 	int         num_labels;
+ 
+ 	num_labels = list_length(stmt->vals);
  
  	/* Convert list of names to a name and namespace */
  	enumNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
***************
*** 1156,1162 **** DefineEnum(CreateEnumStmt *stmt)
  				   false);		/* Type NOT NULL */
  
  	/* Enter the enum's values into pg_enum */
! 	EnumValuesCreate(enumTypeOid, stmt->vals, InvalidOid);
  
  	/*
  	 * Create the array type that goes with it.
--- 1157,1163 ----
  				   false);		/* Type NOT NULL */
  
  	/* Enter the enum's values into pg_enum */
! 	EnumValuesCreate(enumTypeOid, stmt->vals);
  
  	/*
  	 * Create the array type that goes with it.
***************
*** 1197,1202 **** DefineEnum(CreateEnumStmt *stmt)
--- 1198,1259 ----
  	pfree(enumArrayName);
  }
  
+ /*
+  * AlterEnum
+  *		Registers a new label for an existing enum.
+  */
+ void
+ AlterEnum (AlterEnumStmt *stmt)
+ {
+ 	Oid			enum_type_oid;
+ 	TypeName  *typename;
+ 	HeapTuple tup;
+ 	Form_pg_type typTup;
+ 
+ 	/* 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);
+ 
+ 	typTup = (Form_pg_type) GETSTRUCT(tup);
+ 
+ 	/* Check it's an enum and check user has permission to ALTER the enum */
+ 	checkEnumOwner(tup, typename);
+ 
+ 	/* Add the new label */
+ 	AddEnumLabel (enum_type_oid, stmt->newVal, 
+ 				  stmt->newValNeighbour, 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, TypeName *typename)
+ {
+ 	Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup);
+ 
+ 	/* Check that this is actually a domain */
+ 	if (typTup->typtype != TYPTYPE_ENUM)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 				 errmsg("\"%s\" is not an enum",
+ 						TypeNameToString(typename))));
+ 
+ 	/* 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.
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2901,2906 **** _copyCreateEnumStmt(CreateEnumStmt *from)
--- 2901,2919 ----
  	return newnode;
  }
  
+ static AlterEnumStmt *
+ _copyAlterEnumStmt(AlterEnumStmt *from)
+ {
+ 	AlterEnumStmt *newnode = makeNode(AlterEnumStmt);
+ 
+ 	COPY_NODE_FIELD(typeName);
+ 	COPY_STRING_FIELD(newVal);
+ 	COPY_STRING_FIELD(newValNeighbour);
+ 	COPY_SCALAR_FIELD(newValIsAfter);
+ 
+ 	return newnode;
+ }
+ 
  static ViewStmt *
  _copyViewStmt(ViewStmt *from)
  {
***************
*** 4064,4069 **** copyObject(void *from)
--- 4077,4085 ----
  		case T_CreateEnumStmt:
  			retval = _copyCreateEnumStmt(from);
  			break;
+ 		case T_AlterEnumStmt:
+ 			retval = _copyAlterEnumStmt(from);
+ 			break;
  		case T_ViewStmt:
  			retval = _copyViewStmt(from);
  			break;
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 1393,1398 **** _equalCreateEnumStmt(CreateEnumStmt *a, CreateEnumStmt *b)
--- 1393,1409 ----
  }
  
  static bool
+ _equalAlterEnumStmt(AlterEnumStmt *a, AlterEnumStmt *b)
+ {
+ 	COMPARE_NODE_FIELD(typeName);
+ 	COMPARE_STRING_FIELD(newVal);
+ 	COMPARE_STRING_FIELD(newValNeighbour);
+ 	COMPARE_SCALAR_FIELD(newValIsAfter);
+ 
+ 	return true;
+ }
+ 
+ static bool
  _equalViewStmt(ViewStmt *a, ViewStmt *b)
  {
  	COMPARE_NODE_FIELD(view);
***************
*** 2700,2705 **** equal(void *a, void *b)
--- 2711,2719 ----
  		case T_CreateEnumStmt:
  			retval = _equalCreateEnumStmt(a, b);
  			break;
+ 		case T_AlterEnumStmt:
+ 			retval = _equalAlterEnumStmt(a, b);
+ 			break;
  		case T_ViewStmt:
  			retval = _equalViewStmt(a, b);
  			break;
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 182,189 **** static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
  }
  
  %type <node>	stmt schema_stmt
! 		AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterFdwStmt
! 		AlterForeignServerStmt AlterGroupStmt
  		AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
  		AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
  		AlterRoleStmt AlterRoleSetStmt
--- 182,189 ----
  }
  
  %type <node>	stmt schema_stmt
!         AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
!         AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
  		AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
  		AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
  		AlterRoleStmt AlterRoleSetStmt
***************
*** 652,657 **** stmt :
--- 652,658 ----
  			| AlterDatabaseSetStmt
  			| AlterDefaultPrivilegesStmt
  			| AlterDomainStmt
+ 			| AlterEnumStmt
  			| AlterFdwStmt
  			| AlterForeignServerStmt
  			| AlterFunctionStmt
***************
*** 3862,3867 **** enum_val_list:	Sconst
--- 3863,3908 ----
  				{ $$ = 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->newValNeighbour = 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->newValNeighbour = $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->newValNeighbour = $7;
+ 				 n->newValIsAfter = true;
+ 
+ 				 $$ = (Node *) n;
+ 			 }
+ 		 ;
+ 
+ 
  
  /*****************************************************************************
   *
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 190,195 **** check_xact_readonly(Node *parsetree)
--- 190,196 ----
  		case T_CreateTrigStmt:
  		case T_CompositeTypeStmt:
  		case T_CreateEnumStmt:
+ 		case T_AlterEnumStmt:
  		case T_ViewStmt:
  		case T_DropCastStmt:
  		case T_DropStmt:
***************
*** 860,865 **** standard_ProcessUtility(Node *parsetree,
--- 861,870 ----
  			DefineEnum((CreateEnumStmt *) parsetree);
  			break;
  
+ 		case T_AlterEnumStmt:	/* ALTER TYPE (enum) */
+ 			AlterEnum((AlterEnumStmt *) parsetree);
+ 			break;
+ 
  		case T_ViewStmt:		/* CREATE VIEW */
  			DefineView((ViewStmt *) parsetree, queryString);
  			break;
***************
*** 1868,1873 **** CreateCommandTag(Node *parsetree)
--- 1873,1882 ----
  			tag = "CREATE TYPE";
  			break;
  
+ 		case T_AlterEnumStmt:
+ 			tag = "ALTER TYPE";
+ 			break;
+ 
  		case T_ViewStmt:
  			tag = "CREATE VIEW";
  			break;
***************
*** 2410,2415 **** GetCommandLogLevel(Node *parsetree)
--- 2419,2428 ----
  			lev = LOGSTMT_DDL;
  			break;
  
+ 		case T_AlterEnumStmt:
+ 			lev = LOGSTMT_DDL;
+ 			break;
+ 
  		case T_ViewStmt:
  			lev = LOGSTMT_DDL;
  			break;
*** a/src/backend/utils/adt/enum.c
--- b/src/backend/utils/adt/enum.c
***************
*** 14,19 ****
--- 14,20 ----
  #include "postgres.h"
  
  #include "catalog/pg_enum.h"
+ #include "catalog/pg_type.h"
  #include "fmgr.h"
  #include "utils/array.h"
  #include "utils/builtins.h"
***************
*** 22,30 ****
  #include "libpq/pqformat.h"
  #include "miscadmin.h"
  
  
  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 */
--- 23,52 ----
  #include "libpq/pqformat.h"
  #include "miscadmin.h"
  
+ #define BITMAPSIZE 1024
+ #define BITMAPBYTES (BITMAPSIZE / 8)
+ 
+ typedef struct 
+ {
+ 	Oid      enum_oid;
+ 	int32   sort_order;
+ } enum_sort;
+ 
+ typedef struct 
+ {
+ 	Oid       enumtypoid;
+ 	Oid       bitmap_base;
+ 	char      bitmap[BITMAPBYTES];
+ 	int       label_count;
+ 	enum_sort sort_order_list[1];
+ } enum_sort_cache;
+ 	
+ 
  
  static ArrayType *enum_range_internal(Oid enumtypoid, Oid lower, Oid upper);
! static int	enum_sort_cmp(const void *left, const void *right);
! static int	enum_oid_cmp(const void *left, const void *right);
! static enum_sort_cache *initcache(Oid arg, FunctionCallInfo fcinfo);
  
  
  /* Basic I/O support */
***************
*** 155,167 **** enum_send(PG_FUNCTION_ARGS)
  
  /* Comparison functions and related */
  
  Datum
  enum_lt(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a < b);
  }
  
  Datum
--- 177,368 ----
  
  /* Comparison functions and related */
  
+ static enum_sort_cache *
+ initcache(Oid arg, FunctionCallInfo fcinfo)
+ {
+ 	HeapTuple	enum_tup;
+ 	Form_pg_enum en;
+ 	Oid typeoid;
+ 	enum_sort_cache *mycache;
+ 	CatCList *list;
+ 	int num,i, bm_start, bm_len, start_pos, list_end;
+ 
+ 	/* free up anything we've used before) */
+ 	if (fcinfo->flinfo->fn_extra != NULL)
+ 		pfree(fcinfo->flinfo->fn_extra);
+ 
+ 	/* get the typeoid, label count and the list of values */
+ 	enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg));
+ 	en = (Form_pg_enum) GETSTRUCT(enum_tup);
+ 	typeoid = en->enumtypid;
+ 	ReleaseSysCache(enum_tup);
+ 	list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(typeoid));
+ 	num = list->n_members;
+ 	fcinfo->flinfo->fn_extra = 
+ 		MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ 						   sizeof(enum_sort_cache) + 
+ 						   (num * sizeof(enum_sort)));
+ 	mycache = (enum_sort_cache *) fcinfo->flinfo->fn_extra;
+ 	mycache->enumtypoid = typeoid;
+ 	mycache->label_count = num;
+ 	memset(mycache->bitmap, 0, BITMAPBYTES);
+ 
+ 	/* set up the list sorted by Oid */
+ 	for (i = 0; i < num; i++)
+ 	{
+ 		HeapTuple tup = &(list->members[i]->tuple);
+ 		Form_pg_enum list_en = (Form_pg_enum) GETSTRUCT(tup);
+ 
+ 		mycache->sort_order_list[i].sort_order = list_en->enumsortorder;
+ 		mycache->sort_order_list[i].enum_oid = HeapTupleGetOid(tup);
+ 	}
+ 
+ 	ReleaseCatCacheList(list);
+ 
+ 	qsort(mycache->sort_order_list,mycache->label_count,
+ 		  sizeof(enum_sort),enum_oid_cmp);
+ 
+ 
+ 	/* look for the longest suitable range  for the bitmap */
+ 	bm_len = 0;
+ 	bm_start = 0;
+ 	
+     for (start_pos = 0; start_pos < num -1; start_pos ++)
+ 	{
+ 		for (list_end = start_pos+1; list_end < num; list_end++)
+ 			if (mycache->sort_order_list[list_end].sort_order <
+ 				mycache->sort_order_list[list_end - 1].sort_order ||
+ 				mycache->sort_order_list[list_end].enum_oid -
+ 				mycache->sort_order_list[start_pos].enum_oid >= BITMAPSIZE)
+ 				break;
+ 		if (list_end - start_pos > bm_len)
+ 		{
+ 			bm_len = list_end - start_pos;
+ 			bm_start = start_pos;
+ 		}
+ 		if (bm_len == num)
+ 			break;
+ 	}
+ 
+ 	/* if we found a suitable range, for the bitmap, set it up */
+ 	if (bm_len > 1)
+ 	{
+ 		int base = mycache->sort_order_list[bm_start].enum_oid;
+ 
+ 		mycache->bitmap_base = base;
+ 		for (i = 0; i < bm_len; i++)
+ 		{
+ 			Oid enoid = mycache->sort_order_list[bm_start+i].enum_oid;
+ 			int offset = enoid - base;
+ 			int bytenum = offset / 8;
+ 			int bitmask = 1 << (offset % 8);
+ 			mycache->bitmap[bytenum] |= bitmask;
+ 		}
+ 	}
+ 	
+ 	return mycache;
+ }
+ 
+ /* fast lookup for Oids known to be in order */
+ 
+ static inline bool
+ bitmap_lookup(enum_sort_cache *mycache, Oid arg1, Oid arg2)
+ {
+ 	int offset1 = arg1 - mycache->bitmap_base;
+ 	int offset2 = arg2 - mycache->bitmap_base;
+ 	int bytenum, bitmask;
+ 	if (offset1 < 0 || offset2 < 0 || 
+ 		offset1 > BITMAPSIZE || offset2 > BITMAPSIZE)
+ 		return false;
+ 	bytenum = offset1 / 8;
+ 	bitmask = 1 << (offset1 % 8);
+ 	if ((mycache->bitmap[bytenum] & bitmask) == 0)
+ 		return false;
+ 	bytenum = offset2 / 8;
+ 	bitmask = 1 << (offset2 % 8);
+ 	if ((mycache->bitmap[bytenum] & bitmask) == 0)
+ 		return false;
+ 	return true;
+ }
+ 
+ /* slower lookup for Oids not known to be in order */
+ 
+ static inline int
+ find_sortorder(enum_sort_cache *mycache, Oid arg)
+ {
+ 	enum_sort *es;
+ 	enum_sort srch;
+ 
+ 	srch.enum_oid = arg;
+ 	es = bsearch(&srch,
+ 				 mycache->sort_order_list,
+ 				 mycache->label_count,
+ 				 sizeof(enum_sort),
+ 				 enum_oid_cmp);
+ 	if (es == NULL)
+ 		return -1;
+ 	
+ 	return es->sort_order;
+ 
+ }
+ 
+ /* enum_ccmp is the common engine for all the visible comparison functions */
+ 
+ static inline int 
+ enum_ccmp(Oid arg1, Oid arg2, FunctionCallInfo fcinfo)
+ {
+ 
+ 	enum_sort_cache *mycache;
+ 	int sort_1, sort_2;
+ 
+ 	/* evenly numbered Oids are know to sort right */
+ 	if (arg1 % 2 == 0 && arg2 % 2 == 0)
+ 		return arg1 - arg2;
+ 
+ 
+ 	/* so are oids that are equal */
+ 	if (arg1 == arg2)
+ 		return 0;
+ 
+ 	/* get the cached info, set it up if absent */
+ 	mycache = (enum_sort_cache *) fcinfo->flinfo->fn_extra;
+ 	if (mycache == NULL )
+ 		mycache = initcache(arg1, fcinfo);
+ 
+ 	/* first try the fast lookup */
+ 	if (bitmap_lookup(mycache, arg1, arg2))			
+ 		return arg1 - arg2;
+ 	
+ 	/* try the slow lookup  */
+ 	sort_1 = find_sortorder(mycache,arg1);
+ 	sort_2 = find_sortorder(mycache,arg2);
+ 
+ 	if (sort_1 <= 0 || sort_2 <= 0)
+ 	{
+ 		/* 
+ 		 * We couldn't find one or both values.
+ 		 * That means the enum has changed under us, so
+ 		 * re-initialize the cache and try again.
+ 		 */
+ 		mycache = initcache(arg1, fcinfo);
+ 		sort_1 = find_sortorder(mycache,arg1);
+ 		sort_2 = find_sortorder(mycache,arg2);
+ 		/* if we fail the secind time around, give up */
+ 		if (sort_1 <= 0 || sort_2 <= 0)
+ 			elog(ERROR, "values missing for enum %s",
+ 			 format_type_be(mycache->enumtypoid));
+ 	}
+ 
+ 	return sort_1 - sort_2;
+ }
+ 
  Datum
  enum_lt(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) < 0);
  }
  
  Datum
***************
*** 170,176 **** enum_le(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a <= b);
  }
  
  Datum
--- 371,377 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) <= 0);
  }
  
  Datum
***************
*** 197,203 **** enum_ge(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a >= b);
  }
  
  Datum
--- 398,404 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) >= 0);
  }
  
  Datum
***************
*** 206,212 **** enum_gt(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a > b);
  }
  
  Datum
--- 407,413 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) > 0);
  }
  
  Datum
***************
*** 215,221 **** enum_smaller(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(a <= b ? a : b);
  }
  
  Datum
--- 416,422 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(enum_ccmp(a,b,fcinfo) < 0 ? a : b);
  }
  
  Datum
***************
*** 224,230 **** enum_larger(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(a >= b ? a : b);
  }
  
  Datum
--- 425,431 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(enum_ccmp(a,b,fcinfo) > 0 ? a : b);
  }
  
  Datum
***************
*** 233,242 **** 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)
  		PG_RETURN_INT32(0);
  	else
  		PG_RETURN_INT32(-1);
  }
--- 434,443 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	if (a == b)
  		PG_RETURN_INT32(0);
+ 	else if (enum_ccmp(a,b,fcinfo) > 0)
+ 		PG_RETURN_INT32(1);
  	else
  		PG_RETURN_INT32(-1);
  }
***************
*** 248,253 **** enum_first(PG_FUNCTION_ARGS)
--- 449,455 ----
  {
  	Oid			enumtypoid;
  	Oid			min = InvalidOid;
+ 	int         min_sort = -1; /* value will never in fact be used */
  	CatCList   *list;
  	int			num,
  				i;
***************
*** 267,276 **** enum_first(PG_FUNCTION_ARGS)
  	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);
--- 469,482 ----
  	num = list->n_members;
  	for (i = 0; i < num; i++)
  	{
! 		HeapTuple tup = &(list->members[i]->tuple);
! 		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
! 
! 		if (!OidIsValid(min) || en->enumsortorder < min_sort)
! 		{
! 			min = HeapTupleGetOid(tup);
! 			min_sort = en->enumsortorder;
! 		}
  	}
  
  	ReleaseCatCacheList(list);
***************
*** 287,292 **** enum_last(PG_FUNCTION_ARGS)
--- 493,499 ----
  {
  	Oid			enumtypoid;
  	Oid			max = InvalidOid;
+ 	int         max_sort = -1; /* value will never in fact be used */
  	CatCList   *list;
  	int			num,
  				i;
***************
*** 306,315 **** enum_last(PG_FUNCTION_ARGS)
  	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);
--- 513,526 ----
  	num = list->n_members;
  	for (i = 0; i < num; i++)
  	{
! 		HeapTuple tup = &(list->members[i]->tuple);
! 		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
! 
! 		if (!OidIsValid(max) || en->enumsortorder > max_sort)
! 		{
! 			max = HeapTupleGetOid(tup);
! 			max_sort = en->enumsortorder;
! 		}
  	}
  
  	ReleaseCatCacheList(list);
***************
*** 382,427 **** enum_range_internal(Oid enumtypoid, Oid lower, Oid upper)
  				i,
  				j;
  	Datum	   *elems;
  
  	list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid));
  	total = list->n_members;
  
  	elems = (Datum *) palloc(total * sizeof(Datum));
  
- 	j = 0;
  	for (i = 0; i < total; i++)
  	{
! 		Oid			val = HeapTupleGetOid(&(list->members[i]->tuple));
  
- 		if ((!OidIsValid(lower) || lower <= val) &&
- 			(!OidIsValid(upper) || val <= upper))
- 			elems[j++] = ObjectIdGetDatum(val);
  	}
  
  	/* shouldn't need the cache anymore */
  	ReleaseCatCacheList(list);
  
! 	/* sort results into OID order */
! 	qsort(elems, j, sizeof(Datum), enum_elem_cmp);
  
  	/* note this hardwires some details about the representation of Oid */
  	result = construct_array(elems, j, 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;
  }
--- 593,668 ----
  				i,
  				j;
  	Datum	   *elems;
+ 	enum_sort  *sort_items;
+ 	bool        left_found;
  
  	list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid));
  	total = list->n_members;
  
  	elems = (Datum *) palloc(total * sizeof(Datum));
+ 	sort_items = (enum_sort *) palloc(total * sizeof(enum_sort));
  
  	for (i = 0; i < total; i++)
  	{
! 		HeapTuple tup = &(list->members[i]->tuple);
! 		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
! 
! 		sort_items[i].enum_oid = HeapTupleGetOid(tup);
! 		sort_items[i].sort_order = en->enumsortorder;
  
  	}
  
  	/* shouldn't need the cache anymore */
  	ReleaseCatCacheList(list);
  
! 	/* sort results into sort_order sequence */
! 	qsort(sort_items, total, sizeof(enum_sort), enum_sort_cmp);
! 
! 	j = 0; 
! 	left_found = !OidIsValid(lower);
! 	for (i=0; i < total; i++)
! 	{
! 		if (! left_found && lower == sort_items[i].enum_oid)
! 			left_found = true;
! 
! 		if (left_found)
! 			elems[j++] = ObjectIdGetDatum(sort_items[i].enum_oid);
! 
! 		if (OidIsValid(upper) && upper == sort_items[i].enum_oid)
! 			break;
! 	}
  
  	/* note this hardwires some details about the representation of Oid */
  	result = construct_array(elems, j, enumtypoid, sizeof(Oid), true, 'i');
  
  	pfree(elems);
+ 	pfree(sort_items);
  
  	return result;
  }
  
! /* 
!  * qsort comparison using sort order, for range routines 
!  */
  static int
! enum_sort_cmp(const void *left, const void *right)
  {
! 	enum_sort  *l = (enum_sort *) left;
! 	enum_sort  *r = (enum_sort *) right;
  
! 	return l->sort_order - r->sort_order;
! }
! 
! /* 
!  * qsort comparison using OID order for comparison search cache
!  */
! static int 
! enum_oid_cmp(const void *es1, const void *es2)
! {
! 	enum_sort *p1, *p2;
! 	p1 = (enum_sort *)es1;
! 	p2 = (enum_sort *)es2;
! 	return p1->enum_oid - p2->enum_oid;
  }
+ 
+ 
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 6653,6669 **** dumpEnumType(Archive *fout, TypeInfo *tyinfo)
  	PQExpBuffer query = createPQExpBuffer();
  	PGresult   *res;
  	int			num,
! 				i;
  	Oid			enum_oid;
  	char	   *label;
  
  	/* Set proper schema search path so regproc references list correctly */
  	selectSourceSchema(tyinfo->dobj.namespace->dobj.name);
  
! 	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);
--- 6653,6676 ----
  	PQExpBuffer query = createPQExpBuffer();
  	PGresult   *res;
  	int			num,
! 		        i;
  	Oid			enum_oid;
  	char	   *label;
  
  	/* Set proper schema search path so regproc references list correctly */
  	selectSourceSchema(tyinfo->dobj.namespace->dobj.name);
  
!     if  (fout->remoteVersion > 90000)
! 		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);
***************
*** 6709,6725 **** dumpEnumType(Archive *fout, TypeInfo *tyinfo)
  		{
  			enum_oid = atooid(PQgetvalue(res, i, PQfnumber(res, "oid")));
  			label = PQgetvalue(res, i, PQfnumber(res, "enumlabel"));
! 
  			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);
! 			appendStringLiteralAH(q, label, fout);
! 			appendPQExpBuffer(q, ");\n");
  		}
- 		appendPQExpBuffer(q, "\n");
  	}
  
  	ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId,
--- 6716,6734 ----
  		{
  			enum_oid = atooid(PQgetvalue(res, i, PQfnumber(res, "oid")));
  			label = PQgetvalue(res, i, PQfnumber(res, "enumlabel"));
! 			
  			if (i == 0)
  				appendPQExpBuffer(q, "\n-- For binary upgrade, must preserve pg_enum oids\n");
  			appendPQExpBuffer(q,
! 							  "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\n");
  		}
  	}
  
  	ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId,
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
***************
*** 473,489 **** 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"
  						  "      ),\n"
  						  "      E'\\n'\n"
  						  "  ) AS \"%s\",\n",
  						  gettext_noop("Elements"));
  
  	appendPQExpBuffer(&buf,
  				"  pg_catalog.obj_description(t.oid, 'pg_type') as \"%s\"\n",
--- 473,499 ----
  						  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");
! 
! 		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",
*** a/src/include/catalog/indexing.h
--- b/src/include/catalog/indexing.h
***************
*** 147,152 **** DECLARE_UNIQUE_INDEX(pg_enum_oid_index, 3502, on pg_enum using btree(oid oid_ops
--- 147,154 ----
  #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, 3539, on pg_enum using btree(enumtypid oid_ops, enumsortorder int4_ops));
+ #define EnumTypIdSortOrderIndexId 3539
  
  /* 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));
*** a/src/include/catalog/pg_enum.h
--- b/src/include/catalog/pg_enum.h
***************
*** 35,40 **** CATALOG(pg_enum,3501)
--- 35,41 ----
  {
  	Oid			enumtypid;		/* OID of owning enum type */
  	NameData	enumlabel;		/* text representation of enum value */
+ 	int4        enumsortorder;  /* sort order for this enum label */
  } FormData_pg_enum;
  
  /* ----------------
***************
*** 48,56 **** typedef FormData_pg_enum *Form_pg_enum;
   *		compiler constants for pg_enum
   * ----------------
   */
! #define Natts_pg_enum					2
  #define Anum_pg_enum_enumtypid			1
  #define Anum_pg_enum_enumlabel			2
  
  /* ----------------
   *		pg_enum has no initial contents
--- 49,58 ----
   *		compiler constants for pg_enum
   * ----------------
   */
! #define Natts_pg_enum					3
  #define Anum_pg_enum_enumtypid			1
  #define Anum_pg_enum_enumlabel			2
+ #define Anum_pg_enum_enumsortorder		3
  
  /* ----------------
   *		pg_enum has no initial contents
***************
*** 60,67 **** 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 EnumValuesDelete(Oid enumTypeOid);
  
  #endif   /* PG_ENUM_H */
--- 62,70 ----
  /*
   * prototypes for functions in pg_enum.c
   */
! extern void EnumValuesCreate(Oid enumTypeOid, List *vals);
  extern void EnumValuesDelete(Oid enumTypeOid);
+ extern void AddEnumLabel(Oid enumTypeOid, char *newVal, char *neighbour,
+ 						 bool newValIsAfter);
  
  #endif   /* PG_ENUM_H */
*** a/src/include/catalog/pg_type_fn.h
--- b/src/include/catalog/pg_type_fn.h
***************
*** 50,56 **** extern Oid TypeCreate(Oid newTypeOid,
  		   char storage,
  		   int32 typeMod,
  		   int32 typNDims,
! 		   bool typeNotNull);
  
  extern void GenerateTypeDependencies(Oid typeNamespace,
  						 Oid typeObjectId,
--- 50,56 ----
  		   char storage,
  		   int32 typeMod,
  		   int32 typNDims,
!  		   bool typeNotNull);
  
  extern void GenerateTypeDependencies(Oid typeNamespace,
  						 Oid typeObjectId,
*** a/src/include/commands/typecmds.h
--- b/src/include/commands/typecmds.h
***************
*** 24,29 **** extern void RemoveTypes(DropStmt *drop);
--- 24,30 ----
  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);
  
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
***************
*** 351,357 **** typedef enum NodeTag
  	T_DropUserMappingStmt,
  	T_AlterTableSpaceOptionsStmt,
  	T_SecLabelStmt,
! 
  	/*
  	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
  	 */
--- 351,357 ----
  	T_DropUserMappingStmt,
  	T_AlterTableSpaceOptionsStmt,
  	T_SecLabelStmt,
! 	T_AlterEnumStmt,
  	/*
  	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
  	 */
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 2195,2200 **** typedef struct CreateEnumStmt
--- 2195,2214 ----
  
  
  /* ----------------------
+  *		Alter Type Statement, enum types
+  * ----------------------
+  */
+ typedef struct AlterEnumStmt
+ {
+ 	NodeTag		type;
+ 	List	   *typeName;		/* qualified name (list of Value strings) */
+ 	char	   *newVal;			/* new enum value */
+ 	char	   *newValNeighbour;/* neighbouring enum value */
+ 	bool	    newValIsAfter;	/* new enum value is after neighbour? */
+ } AlterEnumStmt;
+ 
+ 
+ /* ----------------------
   *		Create View Statement
   * ----------------------
   */
*** a/src/test/regress/expected/enum.out
--- b/src/test/regress/expected/enum.out
***************
*** 25,30 **** ERROR:  invalid input value for enum rainbow: "mauve"
--- 25,92 ----
  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   |             1
+  venus     |             2
+  earth     |             3
+  mars      |             4
+  jupiter   |             5
+  saturn    |             6
+  uranus    |             7
+  neptune   |             8
+ (8 rows)
+ 
+ select 'mars'::planets > 'mercury' as using_sortorder;
+  using_sortorder 
+ -----------------
+  t
+ (1 row)
+ 
+ -- 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 label.
+ DROP TYPE planets;
+ --
  -- Basic table creation, row selection
  --
  CREATE TABLE enumtest (col rainbow);
***************
*** 403,409 **** 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 
! -----------+-----------
  (0 rows)
  
--- 465,471 ----
  
  SELECT * FROM pg_enum WHERE NOT EXISTS
    (SELECT 1 FROM pg_type WHERE pg_type.oid = enumtypid);
!  enumtypid | enumlabel | enumsortorder 
! -----------+-----------+---------------
  (0 rows)
  
*** a/src/test/regress/sql/enum.sql
--- b/src/test/regress/sql/enum.sql
***************
*** 16,21 **** SELECT 'red'::rainbow;
--- 16,61 ----
  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 'mars'::planets > 'mercury' as using_sortorder;
+ 
+ -- errors for adding labels 
+ ALTER TYPE planets ADD 
+ 	  'plutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutopluto';
+ 
+ ALTER TYPE planets ADD 'pluto' AFTER 'zeus';
+ 
+ DROP TYPE planets;
+ --
  -- Basic table creation, row selection
  --
  CREATE TABLE enumtest (col rainbow);
#72Magnus Hagander
magnus@hagander.net
In reply to: Thom Brown (#70)
Re: WIP: extensible enums

On Tue, Oct 19, 2010 at 10:19, Thom Brown <thom@linux.com> wrote:

On 19 October 2010 05:21, Andrew Dunstan <andrew@dunslane.net> wrote:

On 10/18/2010 10:52 AM, Tom Lane wrote:

We could possibly deal with enum types that follow the existing
convention if we made the cache entry hold a list of all the original,
known-to-be-sorted OIDs.  (This could be reasonably compact and cheap to
probe if it were represented as a starting OID and a Bitmapset of delta
values, since we can assume that the initial set of OIDs is pretty close
together.)  But we have to have that cache entry, and we have to consult
it on every single comparison, so it's definitely going to be slower
than before.

So I'm thinking the comparison procedure goes like this:

1. Both OIDs even?
       If so, just compare them numerically, and we're done.

2. Lookup cache entry for enum type.

3. Both OIDs in list of known-sorted OIDs?
       If so, just compare them numerically, and we're done.

4. Search the part of the cache entry that lists sort positions.
       If not both present, refresh the cache entry.
       If still not present, throw error.

5. Compare by sort positions.

Step 4 is the slowest part but would be avoided in most cases.
However, step 2 is none too speedy either, and would usually
be required when dealing with pre-existing enums.

OK, I've made adjustments that I think do what you're suggesting.

Patch is attached.

Alternatively this can be pulled from
<git@github.com:adunstan/postgresql-dev.git>

Andrew, can't you get your own repo at git.postgresql.org?

He certainly could, but github provides more features and a nicer look
:-) And since it's git, it doesn't matter where the repo is.

--
 Magnus Hagander
 Me: http://www.hagander.net/
 Work: http://www.redpill-linpro.com/

#73Alvaro Herrera
alvherre@commandprompt.com
In reply to: Magnus Hagander (#72)
Re: WIP: extensible enums

Excerpts from Magnus Hagander's message of mar oct 19 05:23:31 -0300 2010:

He certainly could, but github provides more features and a nicer look
:-) And since it's git, it doesn't matter where the repo is.

Yeah. If you have a checked out copy of the GIT repo (preferably one
with the "master" branch in it), try this:

git remote add venum git://github.com/adunstan/postgresql-dev.git
git fetch venum venums:venums
git checkout venums

Then you have the patch in all its Git glory, in branch "venums".

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#74Andrew Dunstan
andrew@dunslane.net
In reply to: Andrew Dunstan (#68)
Re: WIP: extensible enums

On 10/19/2010 12:21 AM, Andrew Dunstan wrote:

On 10/18/2010 10:52 AM, Tom Lane wrote:

We could possibly deal with enum types that follow the existing
convention if we made the cache entry hold a list of all the original,
known-to-be-sorted OIDs. (This could be reasonably compact and cheap to
probe if it were represented as a starting OID and a Bitmapset of delta
values, since we can assume that the initial set of OIDs is pretty close
together.) But we have to have that cache entry, and we have to consult
it on every single comparison, so it's definitely going to be slower
than before.

So I'm thinking the comparison procedure goes like this:

1. Both OIDs even?
If so, just compare them numerically, and we're done.

2. Lookup cache entry for enum type.

3. Both OIDs in list of known-sorted OIDs?
If so, just compare them numerically, and we're done.

4. Search the part of the cache entry that lists sort positions.
If not both present, refresh the cache entry.
If still not present, throw error.

5. Compare by sort positions.

Step 4 is the slowest part but would be avoided in most cases.
However, step 2 is none too speedy either, and would usually
be required when dealing with pre-existing enums.

OK, I've made adjustments that I think do what you're suggesting.

I've discovered and fixed a couple more bugs in this. I have one or two
more things to fix and then I'll send a new patch.

Meanwhile, I've been testing a database that was upgraded from 9.0, so
it has a lot of odd-numbered Oids. It's not really clear from
performance testing that the bitmap is a huge win, or even a win at all.
(Of course, my implementation might suck too.) I'll continue testing.

cheers

andrew

#75Andrew Dunstan
andrew@dunslane.net
In reply to: Andrew Dunstan (#74)
1 attachment(s)
Re: WIP: extensible enums

On 10/19/2010 12:53 PM, Andrew Dunstan wrote:

On 10/19/2010 12:21 AM, Andrew Dunstan wrote:

On 10/18/2010 10:52 AM, Tom Lane wrote:

We could possibly deal with enum types that follow the existing
convention if we made the cache entry hold a list of all the original,
known-to-be-sorted OIDs. (This could be reasonably compact and
cheap to
probe if it were represented as a starting OID and a Bitmapset of delta
values, since we can assume that the initial set of OIDs is pretty
close
together.) But we have to have that cache entry, and we have to
consult
it on every single comparison, so it's definitely going to be slower
than before.

So I'm thinking the comparison procedure goes like this:

1. Both OIDs even?
If so, just compare them numerically, and we're done.

2. Lookup cache entry for enum type.

3. Both OIDs in list of known-sorted OIDs?
If so, just compare them numerically, and we're done.

4. Search the part of the cache entry that lists sort positions.
If not both present, refresh the cache entry.
If still not present, throw error.

5. Compare by sort positions.

Step 4 is the slowest part but would be avoided in most cases.
However, step 2 is none too speedy either, and would usually
be required when dealing with pre-existing enums.

OK, I've made adjustments that I think do what you're suggesting.

I've discovered and fixed a couple more bugs in this. I have one or
two more things to fix and then I'll send a new patch.

Meanwhile, I've been testing a database that was upgraded from 9.0,
so it has a lot of odd-numbered Oids. It's not really clear from
performance testing that the bitmap is a huge win, or even a win at
all. (Of course, my implementation might suck too.) I'll continue
testing.

Well a bit more testing shows some benefit. I've sorted out a few kinks,
so this seems to work. In particular, with the above tables, the version
imported from 9.0 can create have an index created in about the same
time as on the fresh table (identical data, but all even numbered Oids).

Of course, with lots of odd numbered Oids, if a label gets added the
imported version will degrade in performance much more quickly.

The test timed is:

do $$ begin for i in 1 .. 20 loop drop index if exists idx1;
create index idx1 on mydata(label); end loop; end; $$;

Latest patch attached.

cheers

andrew

Attachments:

venum9.patchtext/x-patch; name=venum9.patchDownload
*** a/contrib/pg_upgrade/function.c
--- b/contrib/pg_upgrade/function.c
***************
*** 61,85 **** install_support_functions(migratorContext *ctx)
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 				"		binary_upgrade.set_next_heap_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_toast_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_index_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			 "		binary_upgrade.add_pg_enum_label(OID, OID, NAME) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
--- 61,85 ----
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 					 "		binary_upgrade.set_next_pg_enum_oid(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 				"		binary_upgrade.set_next_heap_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_toast_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
  		PQclear(executeQueryOrDie(ctx, conn,
  								  "CREATE OR REPLACE FUNCTION "
! 			   "		binary_upgrade.set_next_index_relfilenode(OID) "
  								  "RETURNS VOID "
  								  "AS '$libdir/pg_upgrade_support' "
  								  "LANGUAGE C STRICT;"));
*** a/contrib/pg_upgrade_support/pg_upgrade_support.c
--- b/contrib/pg_upgrade_support/pg_upgrade_support.c
***************
*** 30,35 **** PG_MODULE_MAGIC;
--- 30,36 ----
  extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
  extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_array_oid;
  extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_toast_oid;
+ extern PGDLLIMPORT Oid binary_upgrade_next_pg_enum_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;
***************
*** 37,54 **** extern PGDLLIMPORT Oid binary_upgrade_next_index_relfilenode;
  Datum		set_next_pg_type_oid(PG_FUNCTION_ARGS);
  Datum		set_next_pg_type_array_oid(PG_FUNCTION_ARGS);
  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);
  
  PG_FUNCTION_INFO_V1(set_next_pg_type_oid);
  PG_FUNCTION_INFO_V1(set_next_pg_type_array_oid);
  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);
  
  Datum
  set_next_pg_type_oid(PG_FUNCTION_ARGS)
--- 38,55 ----
  Datum		set_next_pg_type_oid(PG_FUNCTION_ARGS);
  Datum		set_next_pg_type_array_oid(PG_FUNCTION_ARGS);
  Datum		set_next_pg_type_toast_oid(PG_FUNCTION_ARGS);
+ Datum       set_next_pg_enum_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);
  
  PG_FUNCTION_INFO_V1(set_next_pg_type_oid);
  PG_FUNCTION_INFO_V1(set_next_pg_type_array_oid);
  PG_FUNCTION_INFO_V1(set_next_pg_type_toast_oid);
+ PG_FUNCTION_INFO_V1(set_next_pg_enum_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);
  
  Datum
  set_next_pg_type_oid(PG_FUNCTION_ARGS)
***************
*** 81,86 **** set_next_pg_type_toast_oid(PG_FUNCTION_ARGS)
--- 82,97 ----
  }
  
  Datum
+ set_next_pg_enum_oid(PG_FUNCTION_ARGS)
+ {
+ 	Oid			enumoid = PG_GETARG_OID(0);
+ 
+ 	binary_upgrade_next_pg_enum_oid = enumoid;
+ 
+ 	PG_RETURN_VOID();
+ }
+ 
+ Datum
  set_next_heap_relfilenode(PG_FUNCTION_ARGS)
  {
  	Oid			relfilenode = PG_GETARG_OID(0);
***************
*** 110,124 **** set_next_index_relfilenode(PG_FUNCTION_ARGS)
  	PG_RETURN_VOID();
  }
  
- Datum
- add_pg_enum_label(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);
- 
- 	PG_RETURN_VOID();
- }
--- 121,123 ----
*** a/doc/src/sgml/ref/alter_type.sgml
--- b/doc/src/sgml/ref/alter_type.sgml
***************
*** 23,33 **** 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> 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>
  
  <phrase>where <replaceable class="PARAMETER">action</replaceable> is one of:</phrase>
  
--- 23,34 ----
  
   <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> 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,108 **** ALTER TYPE <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <replace
--- 104,131 ----
       </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 sort order is not set using <literal>BEFORE</literal> or 
+ 	  <literal>AFTER</literal>, then the new item is placed at the end of
+ 	  the list of values.
+      </para>
+ 	 <note>
+ 	  <para>
+ 	   Adding a new enum value will in some cases lower the comparison and 
+ 	   sorting performance of the enum type. This will only usually occur if  
+ 	   <literal>BEFORE</literal> or <literal>AFTER</literal> are used to set 
+ 	   the new value's sort order position somewhere other than at the end 
+ 	   of the list. Optimal performance will be restored if the database is 
+ 	   dumped and reloaded.
+ 	  </para>
+ 	 </note>
+     </listitem>
+    </varlistentry>
    </variablelist>
    </para>
  
***************
*** 196,201 **** ALTER TYPE <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <replace
--- 219,254 ----
        </listitem>
       </varlistentry>
  
+      <varlistentry>
+       <term><replaceable class="PARAMETER">data_type</replaceable></term>
+       <listitem>
+        <para>
+         The data type of the attribute to add, or the new type of the
+         attribute to alter.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
+      <varlistentry>
+       <term><replaceable class="PARAMETER">new_enum_value</replaceable></term>
+       <listitem>
+        <para>
+         The new value to be added to the num type's list of values. Like all
+ 		enum literals it needs to be quoted.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
+      <varlistentry>
+       <term><replaceable class="PARAMETER">exisiting_enum_value</replaceable></term>
+       <listitem>
+        <para>
+         The neighbour of the new value to be added to the num type's list of values. Like all
+ 		enum literals it needs to be quoted.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
      </variablelist>
     </para>
    </refsect1>
***************
*** 232,237 **** ALTER TYPE email SET SCHEMA customers;
--- 285,297 ----
  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>
  
   <refsect1>
*** a/src/backend/catalog/pg_enum.c
--- b/src/backend/catalog/pg_enum.c
***************
*** 18,29 ****
--- 18,226 ----
  #include "catalog/catalog.h"
  #include "catalog/indexing.h"
  #include "catalog/pg_enum.h"
+ #include "catalog/pg_type.h"
+ #include "utils/lsyscache.h"
+ #include "utils/syscache.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
  #include "utils/rel.h"
  #include "utils/tqual.h"
  
  static int	oid_cmp(const void *p1, const void *p2);
+ static int	sort_order_cmp(const void *p1, const void *p2);
+ 
+ Oid      binary_upgrade_next_pg_enum_oid = InvalidOid;
+ 
+ /*
+  * 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, 
+ 			 char *newVal, 
+ 			 char *neighbour, 
+ 			 bool newValIsAfter)
+ {
+ 	Oid        newOid;
+ 	Relation   pg_enum;
+ 	TupleDesc	tupDesc;
+ 	Datum		values[Natts_pg_enum];
+ 	bool		nulls[Natts_pg_enum];
+ 	NameData	enumlabel;
+ 	HeapTuple   enum_tup;
+ 	int         newelemorder;
+ 	CatCList   *list;
+ 	int nelems;
+ 
+ 	/* 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)));
+ 
+ 	/* get a new OID for the label */
+ 	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
+ 
+ 	list = SearchSysCacheList1(ENUMTYPOIDNAME, 
+ 							   ObjectIdGetDatum(enumTypeOid));
+ 	nelems =  list->n_members;
+ 
+ 	if (neighbour == NULL)
+ 	{
+ 		/* 
+ 		 * Put the new label at the end of the list.
+ 		 * No change to existing tuples is required.
+ 		 */
+ 		newelemorder = nelems + 1;
+ 	}
+ 	else 
+ 	{
+ 		/* BEFORE or AFTER specified */
+ 		int			i;
+ 		HeapTuple    *existing;
+ 		HeapTuple     nbr = NULL;
+ 		Form_pg_enum nbr_en;
+ 
+ 		/* sort the list of the existing elements by enumsortorder */
+ 		existing = palloc(nelems * sizeof(HeapTuple));
+ 
+ 		for (i = 0; i < nelems; i++)
+ 			existing[i] = &(list->members[i]->tuple);
+ 
+ 		qsort(existing, nelems, sizeof(HeapTuple), sort_order_cmp);
+ 		
+ 		/* locate the neighbour element */
+ 		for (i = 0; i < nelems; i++)
+ 		{
+ 			Form_pg_enum exists_en;
+ 			exists_en = (Form_pg_enum) GETSTRUCT(existing[i]);
+ 			if (strcmp(NameStr(exists_en->enumlabel),neighbour) == 0)
+ 				nbr = existing[i];
+ 
+ 		}
+ 
+ 		if (nbr == NULL)
+ 		{
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("\"%s\" is not an existing label.", neighbour)));
+ 		}
+ 
+ 		nbr_en = (Form_pg_enum) GETSTRUCT(nbr);
+ 
+ 		/* 
+ 		 * If BEFORE was specified, the new label goes in the neighbour's
+ 		 * position. Otherwise, it goes in the position after that.
+ 		 */
+ 		newelemorder = nbr_en->enumsortorder;
+ 		if (newValIsAfter)
+ 			newelemorder++;
+ 
+ 		/* 
+ 		 * Add 1 to the sortorder of all the labels after where the
+ 		 * new label goes. Do it from the end back so we don't get
+ 		 * uniqueness violations.
+ 		 */
+ 		for (i = nelems - 1; i>= 0; i--)
+ 		{
+ 			HeapTuple newtup;
+ 			Form_pg_enum exst_en = (Form_pg_enum) GETSTRUCT(existing[i]);
+ 			if (exst_en->enumsortorder < newelemorder)
+ 				break;
+ 			
+ 			newtup = heap_copytuple(existing[i]);
+ 			exst_en = (Form_pg_enum) GETSTRUCT(newtup);
+ 			exst_en->enumsortorder ++;
+ 
+ 			simple_heap_update(pg_enum, &newtup->t_self, newtup);
+ 
+ 			CatalogUpdateIndexes(pg_enum, newtup);
+ 
+ 		}
+ 
+ 	}
+ 
+ 	if (OidIsValid(binary_upgrade_next_pg_enum_oid))
+ 	{
+ 		if (neighbour != NULL)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("BEFORE/AFTER incompatible with binary upgrade.")));
+ 
+ 		newOid = binary_upgrade_next_pg_enum_oid;
+ 		binary_upgrade_next_pg_enum_oid = InvalidOid;
+ 	}
+ 	else
+ 	{
+ 		/* 
+ 		 * Non upgrade case, we allocate a new Oid for the value. 
+ 		 * 
+ 		 * We try to give the new element an even numbered Oid if it's safe,
+ 		 * which should be true if these conditions hold:
+ 		 *   . the Oid of the highest sorted exiting value is even, and 
+ 		 *   . the new value is to sort after that value, and
+ 		 *   . there hasn't been Oid wraparound.
+ 		 * For all other cases we allocate an odd Oid.
+ 		 */
+ 		   
+ 
+ 		newOid = GetNewOid(pg_enum);
+ 		if (newelemorder > nelems)
+ 		{
+ 			Oid last_elem_oid = InvalidOid;
+ 			int i;
+ 
+ 			for (i = nelems - 1; i>= 0; i--)
+ 			{
+ 				Form_pg_enum last_en;
+ 				last_en = (Form_pg_enum) GETSTRUCT(&(list->members[i]->tuple));
+ 				if (last_en->enumsortorder == nelems)
+ 				{
+ 					last_elem_oid = HeapTupleGetOid(&(list->members[i]->tuple));
+ 					break;
+ 				}
+ 			}
+ 
+ 			if (last_elem_oid %2 == 0)
+ 				while ((newOid %2 == 1 && newOid > last_elem_oid) ||
+ 					   (newOid %2 == 0 && newOid < last_elem_oid))
+ 					newOid = GetNewOid(pg_enum);
+ 			else
+ 				while (newOid %2 == 0)
+ 					newOid = GetNewOid(pg_enum);
+ 		}
+ 		else
+ 		{
+ 			while (newOid %2 == 0)
+ 				newOid = GetNewOid(pg_enum);
+ 		}
+ 	}
+ 
+ 	/* set up the new entry */
+ 	tupDesc = pg_enum->rd_att;
+ 	memset(nulls, false, sizeof(nulls));
+ 	values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
+ 	namestrcpy(&enumlabel, newVal);
+ 	values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
+ 	values[Anum_pg_enum_enumsortorder -1] =	Int32GetDatum(newelemorder);
+ 	enum_tup = heap_form_tuple(tupDesc, 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);
+ 
+ 	ReleaseCatCacheList(list);
+ 
+ }
  
  
  /*
***************
*** 33,40 **** 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)
  {
  	Relation	pg_enum;
  	TupleDesc	tupDesc;
--- 230,236 ----
   * vals is a list of Value strings.
   */
  void
! EnumValuesCreate(Oid enumTypeOid, List *vals)
  {
  	Relation	pg_enum;
  	TupleDesc	tupDesc;
***************
*** 50,58 **** EnumValuesCreate(Oid enumTypeOid, List *vals,
  	num_elems = list_length(vals);
  
  	/*
! 	 * XXX 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?
  	 */
  
  	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
--- 246,254 ----
  	num_elems = list_length(vals);
  
  	/*
! 	 * 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.
! 	 * It is probably not worth trying harder.
  	 */
  
  	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
***************
*** 62,96 **** EnumValuesCreate(Oid enumTypeOid, List *vals,
  	 * Allocate oids
  	 */
  	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
  	{
  		/*
! 		 * 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.
  		 */
! 		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);
  	}
  
  	/* and make the entries */
  	memset(nulls, false, sizeof(nulls));
--- 258,287 ----
  	 * Allocate oids
  	 */
  	oids = (Oid *) palloc(num_elems * sizeof(Oid));
! 
! 	/*
! 	 * 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.
! 	 */
! 	for (elemno = 0; elemno < num_elems; elemno++)
  	{
  		/*
! 		 * The pg_enum.oid is stored in user tables.  This oid must be
! 		 * preserved by binary upgrades.
! 		 *
! 		 * We allocate all new enums as evenly numbered Oids
! 		 * so they will sort fast.
  		 */
! 		Oid new_oid;
! 		while ((new_oid = GetNewOid(pg_enum)) %2 == 1)
! 			;
! 		oids[elemno] = new_oid;
  	}
+ 	/* sort them, just in case counter wrapped from high to low */
+ 	qsort(oids, num_elems, sizeof(Oid), oid_cmp);
  
  	/* and make the entries */
  	memset(nulls, false, sizeof(nulls));
***************
*** 114,119 **** EnumValuesCreate(Oid enumTypeOid, List *vals,
--- 305,311 ----
  		values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
  		namestrcpy(&enumlabel, lab);
  		values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
+ 		values[Anum_pg_enum_enumsortorder -1] = Int32GetDatum(elemno+1);
  
  		tup = heap_form_tuple(tupDesc, values, nulls);
  		HeapTupleSetOid(tup, oids[elemno]);
***************
*** 164,170 **** EnumValuesDelete(Oid enumTypeOid)
  }
  
  
! /* qsort comparison function */
  static int
  oid_cmp(const void *p1, const void *p2)
  {
--- 356,362 ----
  }
  
  
! /* qsort comparison for oids */
  static int
  oid_cmp(const void *p1, const void *p2)
  {
***************
*** 177,179 **** oid_cmp(const void *p1, const void *p2)
--- 369,384 ----
  		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);
+ 
+ 	return en1->enumsortorder - en2->enumsortorder;
+ }
+ 
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
***************
*** 85,96 **** 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 char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
  					Oid baseTypeOid,
  					int typMod, Constraint *constr,
  					char *domainName);
- 
- 
  /*
   * DefineType
   *		Registers a new base type.
--- 85,95 ----
  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 checkEnumOwner(HeapTuple tup, TypeName *typename);
  static char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
  					Oid baseTypeOid,
  					int typMod, Constraint *constr,
  					char *domainName);
  /*
   * DefineType
   *		Registers a new base type.
***************
*** 563,569 **** DefineType(List *names, List *parameters)
  				   -1,			/* typMod (Domains only) */
  				   0,			/* Array Dimensions of typbasetype */
  				   false);		/* Type NOT NULL */
- 
  	/*
  	 * Create the array type that goes with it.
  	 */
--- 562,567 ----
***************
*** 1044,1050 **** DefineDomain(CreateDomainStmt *stmt)
  				   storage,		/* TOAST strategy */
  				   basetypeMod, /* typeMod value */
  				   typNDims,	/* Array dimensions for base type */
! 				   typNotNull); /* Type NOT NULL */
  
  	/*
  	 * Process constraints which refer to the domain ID returned by TypeCreate
--- 1042,1048 ----
  				   storage,		/* TOAST strategy */
  				   basetypeMod, /* typeMod value */
  				   typNDims,	/* Array dimensions for base type */
! 				   typNotNull);  /* Type NOT NULL */
  
  	/*
  	 * Process constraints which refer to the domain ID returned by TypeCreate
***************
*** 1094,1099 **** DefineEnum(CreateEnumStmt *stmt)
--- 1092,1100 ----
  	AclResult	aclresult;
  	Oid			old_type_oid;
  	Oid			enumArrayOid;
+ 	int         num_labels;
+ 
+ 	num_labels = list_length(stmt->vals);
  
  	/* Convert list of names to a name and namespace */
  	enumNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
***************
*** 1156,1162 **** DefineEnum(CreateEnumStmt *stmt)
  				   false);		/* Type NOT NULL */
  
  	/* Enter the enum's values into pg_enum */
! 	EnumValuesCreate(enumTypeOid, stmt->vals, InvalidOid);
  
  	/*
  	 * Create the array type that goes with it.
--- 1157,1163 ----
  				   false);		/* Type NOT NULL */
  
  	/* Enter the enum's values into pg_enum */
! 	EnumValuesCreate(enumTypeOid, stmt->vals);
  
  	/*
  	 * Create the array type that goes with it.
***************
*** 1197,1202 **** DefineEnum(CreateEnumStmt *stmt)
--- 1198,1259 ----
  	pfree(enumArrayName);
  }
  
+ /*
+  * AlterEnum
+  *		Registers a new label for an existing enum.
+  */
+ void
+ AlterEnum (AlterEnumStmt *stmt)
+ {
+ 	Oid			enum_type_oid;
+ 	TypeName  *typename;
+ 	HeapTuple tup;
+ 	Form_pg_type typTup;
+ 
+ 	/* 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);
+ 
+ 	typTup = (Form_pg_type) GETSTRUCT(tup);
+ 
+ 	/* Check it's an enum and check user has permission to ALTER the enum */
+ 	checkEnumOwner(tup, typename);
+ 
+ 	/* Add the new label */
+ 	AddEnumLabel (enum_type_oid, stmt->newVal, 
+ 				  stmt->newValNeighbour, 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, TypeName *typename)
+ {
+ 	Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup);
+ 
+ 	/* Check that this is actually a domain */
+ 	if (typTup->typtype != TYPTYPE_ENUM)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 				 errmsg("\"%s\" is not an enum",
+ 						TypeNameToString(typename))));
+ 
+ 	/* 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.
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2901,2906 **** _copyCreateEnumStmt(CreateEnumStmt *from)
--- 2901,2919 ----
  	return newnode;
  }
  
+ static AlterEnumStmt *
+ _copyAlterEnumStmt(AlterEnumStmt *from)
+ {
+ 	AlterEnumStmt *newnode = makeNode(AlterEnumStmt);
+ 
+ 	COPY_NODE_FIELD(typeName);
+ 	COPY_STRING_FIELD(newVal);
+ 	COPY_STRING_FIELD(newValNeighbour);
+ 	COPY_SCALAR_FIELD(newValIsAfter);
+ 
+ 	return newnode;
+ }
+ 
  static ViewStmt *
  _copyViewStmt(ViewStmt *from)
  {
***************
*** 4064,4069 **** copyObject(void *from)
--- 4077,4085 ----
  		case T_CreateEnumStmt:
  			retval = _copyCreateEnumStmt(from);
  			break;
+ 		case T_AlterEnumStmt:
+ 			retval = _copyAlterEnumStmt(from);
+ 			break;
  		case T_ViewStmt:
  			retval = _copyViewStmt(from);
  			break;
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 1393,1398 **** _equalCreateEnumStmt(CreateEnumStmt *a, CreateEnumStmt *b)
--- 1393,1409 ----
  }
  
  static bool
+ _equalAlterEnumStmt(AlterEnumStmt *a, AlterEnumStmt *b)
+ {
+ 	COMPARE_NODE_FIELD(typeName);
+ 	COMPARE_STRING_FIELD(newVal);
+ 	COMPARE_STRING_FIELD(newValNeighbour);
+ 	COMPARE_SCALAR_FIELD(newValIsAfter);
+ 
+ 	return true;
+ }
+ 
+ static bool
  _equalViewStmt(ViewStmt *a, ViewStmt *b)
  {
  	COMPARE_NODE_FIELD(view);
***************
*** 2700,2705 **** equal(void *a, void *b)
--- 2711,2719 ----
  		case T_CreateEnumStmt:
  			retval = _equalCreateEnumStmt(a, b);
  			break;
+ 		case T_AlterEnumStmt:
+ 			retval = _equalAlterEnumStmt(a, b);
+ 			break;
  		case T_ViewStmt:
  			retval = _equalViewStmt(a, b);
  			break;
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 182,189 **** static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
  }
  
  %type <node>	stmt schema_stmt
! 		AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterFdwStmt
! 		AlterForeignServerStmt AlterGroupStmt
  		AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
  		AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
  		AlterRoleStmt AlterRoleSetStmt
--- 182,189 ----
  }
  
  %type <node>	stmt schema_stmt
!         AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
!         AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
  		AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
  		AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
  		AlterRoleStmt AlterRoleSetStmt
***************
*** 652,657 **** stmt :
--- 652,658 ----
  			| AlterDatabaseSetStmt
  			| AlterDefaultPrivilegesStmt
  			| AlterDomainStmt
+ 			| AlterEnumStmt
  			| AlterFdwStmt
  			| AlterForeignServerStmt
  			| AlterFunctionStmt
***************
*** 3862,3867 **** enum_val_list:	Sconst
--- 3863,3908 ----
  				{ $$ = 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->newValNeighbour = 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->newValNeighbour = $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->newValNeighbour = $7;
+ 				 n->newValIsAfter = true;
+ 
+ 				 $$ = (Node *) n;
+ 			 }
+ 		 ;
+ 
+ 
  
  /*****************************************************************************
   *
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 190,195 **** check_xact_readonly(Node *parsetree)
--- 190,196 ----
  		case T_CreateTrigStmt:
  		case T_CompositeTypeStmt:
  		case T_CreateEnumStmt:
+ 		case T_AlterEnumStmt:
  		case T_ViewStmt:
  		case T_DropCastStmt:
  		case T_DropStmt:
***************
*** 860,865 **** standard_ProcessUtility(Node *parsetree,
--- 861,870 ----
  			DefineEnum((CreateEnumStmt *) parsetree);
  			break;
  
+ 		case T_AlterEnumStmt:	/* ALTER TYPE (enum) */
+ 			AlterEnum((AlterEnumStmt *) parsetree);
+ 			break;
+ 
  		case T_ViewStmt:		/* CREATE VIEW */
  			DefineView((ViewStmt *) parsetree, queryString);
  			break;
***************
*** 1868,1873 **** CreateCommandTag(Node *parsetree)
--- 1873,1882 ----
  			tag = "CREATE TYPE";
  			break;
  
+ 		case T_AlterEnumStmt:
+ 			tag = "ALTER TYPE";
+ 			break;
+ 
  		case T_ViewStmt:
  			tag = "CREATE VIEW";
  			break;
***************
*** 2410,2415 **** GetCommandLogLevel(Node *parsetree)
--- 2419,2428 ----
  			lev = LOGSTMT_DDL;
  			break;
  
+ 		case T_AlterEnumStmt:
+ 			lev = LOGSTMT_DDL;
+ 			break;
+ 
  		case T_ViewStmt:
  			lev = LOGSTMT_DDL;
  			break;
*** a/src/backend/utils/adt/enum.c
--- b/src/backend/utils/adt/enum.c
***************
*** 14,19 ****
--- 14,20 ----
  #include "postgres.h"
  
  #include "catalog/pg_enum.h"
+ #include "catalog/pg_type.h"
  #include "fmgr.h"
  #include "utils/array.h"
  #include "utils/builtins.h"
***************
*** 22,30 ****
  #include "libpq/pqformat.h"
  #include "miscadmin.h"
  
  
  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 */
--- 23,52 ----
  #include "libpq/pqformat.h"
  #include "miscadmin.h"
  
+ #define BITMAPSIZE 1024
+ #define BITMAPBYTES (BITMAPSIZE / 8)
+ 
+ typedef struct 
+ {
+ 	Oid      enum_oid;
+ 	int32   sort_order;
+ } enum_sort;
+ 
+ typedef struct 
+ {
+ 	Oid       enumtypoid;
+ 	Oid       bitmap_base;
+ 	char      bitmap[BITMAPBYTES];
+ 	int       label_count;
+ 	enum_sort sort_order_list[1];
+ } enum_sort_cache;
+ 	
+ 
  
  static ArrayType *enum_range_internal(Oid enumtypoid, Oid lower, Oid upper);
! static int	enum_sort_cmp(const void *left, const void *right);
! static int	enum_oid_cmp(const void *left, const void *right);
! static enum_sort_cache *initcache(Oid arg, FunctionCallInfo fcinfo);
  
  
  /* Basic I/O support */
***************
*** 155,167 **** enum_send(PG_FUNCTION_ARGS)
  
  /* Comparison functions and related */
  
  Datum
  enum_lt(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a < b);
  }
  
  Datum
--- 177,390 ----
  
  /* Comparison functions and related */
  
+ static enum_sort_cache *
+ initcache(Oid arg, FunctionCallInfo fcinfo)
+ {
+ 	HeapTuple	enum_tup;
+ 	Form_pg_enum en;
+ 	Oid typeoid;
+ 	enum_sort_cache *mycache;
+ 	CatCList *list;
+ 	int num,i, bm_start, bm_len, start_pos, list_end;
+ 
+ 	/* free up anything we've used before) */
+ 	if (fcinfo->flinfo->fn_extra != NULL)
+ 		pfree(fcinfo->flinfo->fn_extra);
+ 
+ 	/* get the typeoid, label count and the list of values */
+ 	enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg));
+ 	en = (Form_pg_enum) GETSTRUCT(enum_tup);
+ 	typeoid = en->enumtypid;
+ 	ReleaseSysCache(enum_tup);
+ 	list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(typeoid));
+ 	num = list->n_members;
+ 	fcinfo->flinfo->fn_extra = 
+ 		MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ 						   sizeof(enum_sort_cache) + 
+ 						   (num * sizeof(enum_sort)));
+ 	mycache = (enum_sort_cache *) fcinfo->flinfo->fn_extra;
+ 	mycache->enumtypoid = typeoid;
+ 	mycache->label_count = num;
+ 	memset(mycache->bitmap, 0, BITMAPBYTES);
+ 
+ 	/* set up the list sorted by Oid */
+ 	for (i = 0; i < num; i++)
+ 	{
+ 		HeapTuple tup = &(list->members[i]->tuple);
+ 		Form_pg_enum list_en = (Form_pg_enum) GETSTRUCT(tup);
+ 
+ 		mycache->sort_order_list[i].sort_order = list_en->enumsortorder;
+ 		mycache->sort_order_list[i].enum_oid = HeapTupleGetOid(tup);
+ 	}
+ 
+ 	ReleaseCatCacheList(list);
+ 
+ 	qsort(mycache->sort_order_list,mycache->label_count,
+ 		  sizeof(enum_sort),enum_oid_cmp);
+ 
+ 
+ 	/* look for the longest suitable range  for the bitmap */
+ 
+ 	bm_len = 0;
+ 	bm_start = 0;
+ 
+     for (start_pos = 0; start_pos < num -1; start_pos ++)
+ 	{
+ 		for (list_end = start_pos+1; list_end < num; list_end++)
+ 			if (mycache->sort_order_list[list_end].sort_order <
+ 				mycache->sort_order_list[list_end - 1].sort_order ||
+ 				mycache->sort_order_list[list_end].enum_oid -
+ 				mycache->sort_order_list[start_pos].enum_oid >= BITMAPSIZE)
+ 				break;
+ 		if (list_end - start_pos > bm_len)
+ 		{
+ 			bm_len = list_end - start_pos;
+ 			bm_start = start_pos;
+ 		}
+ 		/* 
+ 		 * If the sequence we've just found is bigger than half the list,
+ 		 *  we won't find one bigger 
+ 		 */
+ 		if (bm_len > num / 2)
+ 			break;
+ 		/*
+ 		 * If we didn't stop because the oid range was too big, there's no
+ 		 * point starting to look again earlier than the end of the 
+ 		 * sequence we just found.
+ 		 */
+ 		if (mycache->sort_order_list[list_end].enum_oid -
+ 			mycache->sort_order_list[start_pos].enum_oid >= BITMAPSIZE)
+ 			start_pos = list_end - 1;
+ 	}
+ 
+ 	/* if we found a suitable range, for the bitmap, set it up */
+ 	if (bm_len > 1)
+ 	{
+ 		int base = mycache->sort_order_list[bm_start].enum_oid;
+ 
+ 		mycache->bitmap_base = base;
+ 		for (i = 0; i < bm_len; i++)
+ 		{
+ 			Oid enoid = mycache->sort_order_list[bm_start+i].enum_oid;
+ 			int offset = enoid - base;
+ 			int bytenum = offset / 8;
+ 			int bitmask = 1 << (offset % 8);
+ 			mycache->bitmap[bytenum] |= bitmask;
+ 		}
+ 	}
+ 	else
+ 	{
+ 		/* 
+ 		 * This should be a bit of a pathological case. Normally we'd
+ 		 * expect at least some small range in sequence.
+ 		 */
+ 		mycache->bitmap_base = InvalidOid;
+ 	}
+ 	
+ 	return mycache;
+ }
+ 
+ /* fast lookup for Oids known to be in order */
+ 
+ static inline bool
+ bitmap_lookup(enum_sort_cache *mycache, Oid arg1, Oid arg2)
+ {
+ 	int offset1 = arg1 - mycache->bitmap_base;
+ 	int offset2 = arg2 - mycache->bitmap_base;
+ 	int bytenum, bitmask;
+ 	if (offset1 < 0 || offset2 < 0 || 
+ 		offset1 >= BITMAPSIZE || offset2 >= BITMAPSIZE)
+ 		return false;
+ 	bytenum = offset1 / 8;
+ 	bitmask = 1 << (offset1 % 8);
+ 	if ((mycache->bitmap[bytenum] & bitmask) == 0)
+ 		return false;
+ 	bytenum = offset2 / 8;
+ 	bitmask = 1 << (offset2 % 8);
+ 	if ((mycache->bitmap[bytenum] & bitmask) == 0)
+ 		return false;
+ 	return true;
+ }
+ 
+ /* slower lookup for Oids not known to be in order */
+ 
+ static inline int
+ find_sortorder(enum_sort_cache *mycache, Oid arg)
+ {
+ 	enum_sort *es;
+ 	enum_sort srch;
+ 
+ 	srch.enum_oid = arg;
+ 	es = bsearch(&srch,
+ 				 mycache->sort_order_list,
+ 				 mycache->label_count,
+ 				 sizeof(enum_sort),
+ 				 enum_oid_cmp);
+ 	if (es == NULL)
+ 		return -1;
+ 	
+ 	return es->sort_order;
+ 
+ }
+ 
+ /* enum_ccmp is the common engine for all the visible comparison functions */
+ 
+ static inline int 
+ enum_ccmp(Oid arg1, Oid arg2, FunctionCallInfo fcinfo)
+ {
+ 
+ 	enum_sort_cache *mycache;
+ 	int sort_1, sort_2;
+ 
+ 	/* evenly numbered Oids are known to sort right */
+ 	if (arg1 % 2 == 0 && arg2 % 2 == 0)
+ 		return arg1 - arg2;
+ 
+ 
+ 	/* so are oids that are equal */
+ 	if (arg1 == arg2)
+ 		return 0;
+ 
+ 	/* get the cached info, set it up if absent */
+ 	mycache = (enum_sort_cache *) fcinfo->flinfo->fn_extra;
+ 	if (mycache == NULL )
+ 		mycache = initcache(arg1, fcinfo);
+ 
+ 	/* first try the fast lookup, if we can */
+ 	if (OidIsValid(mycache->bitmap_base) && 
+ 		bitmap_lookup(mycache, arg1, arg2))			
+ 		return arg1 - arg2;
+ 	
+ 	/* try the slow lookup  */
+ 	sort_1 = find_sortorder(mycache,arg1);
+ 	sort_2 = find_sortorder(mycache,arg2);
+ 
+ 	if (sort_1 <= 0 || sort_2 <= 0)
+ 	{
+ 		/* 
+ 		 * We couldn't find one or both values.
+ 		 * That means the enum has changed under us, so
+ 		 * re-initialize the cache and try again.
+ 		 */
+ 		mycache = initcache(arg1, fcinfo);
+ 		sort_1 = find_sortorder(mycache,arg1);
+ 		sort_2 = find_sortorder(mycache,arg2);
+ 		/* if we fail the second time around, give up */
+ 		if (sort_1 <= 0 || sort_2 <= 0)
+ 			elog(ERROR, "values missing for enum %s",
+ 			 format_type_be(mycache->enumtypoid));
+ 	}
+ 
+ 	return sort_1 - sort_2;
+ }
+ 
  Datum
  enum_lt(PG_FUNCTION_ARGS)
  {
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) < 0);
  }
  
  Datum
***************
*** 170,176 **** enum_le(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a <= b);
  }
  
  Datum
--- 393,399 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) <= 0);
  }
  
  Datum
***************
*** 197,203 **** enum_ge(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a >= b);
  }
  
  Datum
--- 420,426 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) >= 0);
  }
  
  Datum
***************
*** 206,212 **** enum_gt(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(a > b);
  }
  
  Datum
--- 429,435 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_BOOL(enum_ccmp(a,b,fcinfo) > 0);
  }
  
  Datum
***************
*** 215,221 **** enum_smaller(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(a <= b ? a : b);
  }
  
  Datum
--- 438,444 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(enum_ccmp(a,b,fcinfo) < 0 ? a : b);
  }
  
  Datum
***************
*** 224,230 **** enum_larger(PG_FUNCTION_ARGS)
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(a >= b ? a : b);
  }
  
  Datum
--- 447,453 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	PG_RETURN_OID(enum_ccmp(a,b,fcinfo) > 0 ? a : b);
  }
  
  Datum
***************
*** 233,242 **** 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)
  		PG_RETURN_INT32(0);
  	else
  		PG_RETURN_INT32(-1);
  }
--- 456,465 ----
  	Oid			a = PG_GETARG_OID(0);
  	Oid			b = PG_GETARG_OID(1);
  
! 	if (a == b)
  		PG_RETURN_INT32(0);
+ 	else if (enum_ccmp(a,b,fcinfo) > 0)
+ 		PG_RETURN_INT32(1);
  	else
  		PG_RETURN_INT32(-1);
  }
***************
*** 248,253 **** enum_first(PG_FUNCTION_ARGS)
--- 471,477 ----
  {
  	Oid			enumtypoid;
  	Oid			min = InvalidOid;
+ 	int         min_sort = -1; /* value will never in fact be used */
  	CatCList   *list;
  	int			num,
  				i;
***************
*** 267,276 **** enum_first(PG_FUNCTION_ARGS)
  	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);
--- 491,504 ----
  	num = list->n_members;
  	for (i = 0; i < num; i++)
  	{
! 		HeapTuple tup = &(list->members[i]->tuple);
! 		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
! 
! 		if (!OidIsValid(min) || en->enumsortorder < min_sort)
! 		{
! 			min = HeapTupleGetOid(tup);
! 			min_sort = en->enumsortorder;
! 		}
  	}
  
  	ReleaseCatCacheList(list);
***************
*** 287,292 **** enum_last(PG_FUNCTION_ARGS)
--- 515,521 ----
  {
  	Oid			enumtypoid;
  	Oid			max = InvalidOid;
+ 	int         max_sort = -1; /* value will never in fact be used */
  	CatCList   *list;
  	int			num,
  				i;
***************
*** 306,315 **** enum_last(PG_FUNCTION_ARGS)
  	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);
--- 535,548 ----
  	num = list->n_members;
  	for (i = 0; i < num; i++)
  	{
! 		HeapTuple tup = &(list->members[i]->tuple);
! 		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
! 
! 		if (!OidIsValid(max) || en->enumsortorder > max_sort)
! 		{
! 			max = HeapTupleGetOid(tup);
! 			max_sort = en->enumsortorder;
! 		}
  	}
  
  	ReleaseCatCacheList(list);
***************
*** 382,427 **** enum_range_internal(Oid enumtypoid, Oid lower, Oid upper)
  				i,
  				j;
  	Datum	   *elems;
  
  	list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid));
  	total = list->n_members;
  
  	elems = (Datum *) palloc(total * sizeof(Datum));
  
- 	j = 0;
  	for (i = 0; i < total; i++)
  	{
! 		Oid			val = HeapTupleGetOid(&(list->members[i]->tuple));
  
- 		if ((!OidIsValid(lower) || lower <= val) &&
- 			(!OidIsValid(upper) || val <= upper))
- 			elems[j++] = ObjectIdGetDatum(val);
  	}
  
  	/* shouldn't need the cache anymore */
  	ReleaseCatCacheList(list);
  
! 	/* sort results into OID order */
! 	qsort(elems, j, sizeof(Datum), enum_elem_cmp);
  
  	/* note this hardwires some details about the representation of Oid */
  	result = construct_array(elems, j, 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;
  }
--- 615,690 ----
  				i,
  				j;
  	Datum	   *elems;
+ 	enum_sort  *sort_items;
+ 	bool        left_found;
  
  	list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid));
  	total = list->n_members;
  
  	elems = (Datum *) palloc(total * sizeof(Datum));
+ 	sort_items = (enum_sort *) palloc(total * sizeof(enum_sort));
  
  	for (i = 0; i < total; i++)
  	{
! 		HeapTuple tup = &(list->members[i]->tuple);
! 		Form_pg_enum en = (Form_pg_enum) GETSTRUCT(tup);
! 
! 		sort_items[i].enum_oid = HeapTupleGetOid(tup);
! 		sort_items[i].sort_order = en->enumsortorder;
  
  	}
  
  	/* shouldn't need the cache anymore */
  	ReleaseCatCacheList(list);
  
! 	/* sort results into sort_order sequence */
! 	qsort(sort_items, total, sizeof(enum_sort), enum_sort_cmp);
! 
! 	j = 0; 
! 	left_found = !OidIsValid(lower);
! 	for (i=0; i < total; i++)
! 	{
! 		if (! left_found && lower == sort_items[i].enum_oid)
! 			left_found = true;
! 
! 		if (left_found)
! 			elems[j++] = ObjectIdGetDatum(sort_items[i].enum_oid);
! 
! 		if (OidIsValid(upper) && upper == sort_items[i].enum_oid)
! 			break;
! 	}
  
  	/* note this hardwires some details about the representation of Oid */
  	result = construct_array(elems, j, enumtypoid, sizeof(Oid), true, 'i');
  
  	pfree(elems);
+ 	pfree(sort_items);
  
  	return result;
  }
  
! /* 
!  * qsort comparison using sort order, for range routines 
!  */
  static int
! enum_sort_cmp(const void *left, const void *right)
  {
! 	enum_sort  *l = (enum_sort *) left;
! 	enum_sort  *r = (enum_sort *) right;
  
! 	return l->sort_order - r->sort_order;
! }
! 
! /* 
!  * qsort comparison using OID order for comparison search cache
!  */
! static int 
! enum_oid_cmp(const void *es1, const void *es2)
! {
! 	enum_sort *p1, *p2;
! 	p1 = (enum_sort *)es1;
! 	p2 = (enum_sort *)es2;
! 	return p1->enum_oid - p2->enum_oid;
  }
+ 
+ 
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 6653,6669 **** dumpEnumType(Archive *fout, TypeInfo *tyinfo)
  	PQExpBuffer query = createPQExpBuffer();
  	PGresult   *res;
  	int			num,
! 				i;
  	Oid			enum_oid;
  	char	   *label;
  
  	/* Set proper schema search path so regproc references list correctly */
  	selectSourceSchema(tyinfo->dobj.namespace->dobj.name);
  
! 	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);
--- 6653,6676 ----
  	PQExpBuffer query = createPQExpBuffer();
  	PGresult   *res;
  	int			num,
! 		        i;
  	Oid			enum_oid;
  	char	   *label;
  
  	/* Set proper schema search path so regproc references list correctly */
  	selectSourceSchema(tyinfo->dobj.namespace->dobj.name);
  
!     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);
***************
*** 6709,6725 **** dumpEnumType(Archive *fout, TypeInfo *tyinfo)
  		{
  			enum_oid = atooid(PQgetvalue(res, i, PQfnumber(res, "oid")));
  			label = PQgetvalue(res, i, PQfnumber(res, "enumlabel"));
! 
  			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);
! 			appendStringLiteralAH(q, label, fout);
! 			appendPQExpBuffer(q, ");\n");
  		}
- 		appendPQExpBuffer(q, "\n");
  	}
  
  	ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId,
--- 6716,6734 ----
  		{
  			enum_oid = atooid(PQgetvalue(res, i, PQfnumber(res, "oid")));
  			label = PQgetvalue(res, i, PQfnumber(res, "enumlabel"));
! 			
  			if (i == 0)
  				appendPQExpBuffer(q, "\n-- For binary upgrade, must preserve pg_enum oids\n");
  			appendPQExpBuffer(q,
! 							  "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\n");
  		}
  	}
  
  	ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId,
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
***************
*** 473,489 **** 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"
  						  "      ),\n"
  						  "      E'\\n'\n"
  						  "  ) AS \"%s\",\n",
  						  gettext_noop("Elements"));
  
  	appendPQExpBuffer(&buf,
  				"  pg_catalog.obj_description(t.oid, 'pg_type') as \"%s\"\n",
--- 473,499 ----
  						  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");
! 
! 		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",
*** a/src/include/catalog/indexing.h
--- b/src/include/catalog/indexing.h
***************
*** 147,152 **** DECLARE_UNIQUE_INDEX(pg_enum_oid_index, 3502, on pg_enum using btree(oid oid_ops
--- 147,154 ----
  #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, 3539, on pg_enum using btree(enumtypid oid_ops, enumsortorder int4_ops));
+ #define EnumTypIdSortOrderIndexId 3539
  
  /* 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));
*** a/src/include/catalog/pg_enum.h
--- b/src/include/catalog/pg_enum.h
***************
*** 35,40 **** CATALOG(pg_enum,3501)
--- 35,41 ----
  {
  	Oid			enumtypid;		/* OID of owning enum type */
  	NameData	enumlabel;		/* text representation of enum value */
+ 	int4        enumsortorder;  /* sort order for this enum label */
  } FormData_pg_enum;
  
  /* ----------------
***************
*** 48,56 **** typedef FormData_pg_enum *Form_pg_enum;
   *		compiler constants for pg_enum
   * ----------------
   */
! #define Natts_pg_enum					2
  #define Anum_pg_enum_enumtypid			1
  #define Anum_pg_enum_enumlabel			2
  
  /* ----------------
   *		pg_enum has no initial contents
--- 49,58 ----
   *		compiler constants for pg_enum
   * ----------------
   */
! #define Natts_pg_enum					3
  #define Anum_pg_enum_enumtypid			1
  #define Anum_pg_enum_enumlabel			2
+ #define Anum_pg_enum_enumsortorder		3
  
  /* ----------------
   *		pg_enum has no initial contents
***************
*** 60,67 **** 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 EnumValuesDelete(Oid enumTypeOid);
  
  #endif   /* PG_ENUM_H */
--- 62,70 ----
  /*
   * prototypes for functions in pg_enum.c
   */
! extern void EnumValuesCreate(Oid enumTypeOid, List *vals);
  extern void EnumValuesDelete(Oid enumTypeOid);
+ extern void AddEnumLabel(Oid enumTypeOid, char *newVal, char *neighbour,
+ 						 bool newValIsAfter);
  
  #endif   /* PG_ENUM_H */
*** a/src/include/catalog/pg_type_fn.h
--- b/src/include/catalog/pg_type_fn.h
***************
*** 50,56 **** extern Oid TypeCreate(Oid newTypeOid,
  		   char storage,
  		   int32 typeMod,
  		   int32 typNDims,
! 		   bool typeNotNull);
  
  extern void GenerateTypeDependencies(Oid typeNamespace,
  						 Oid typeObjectId,
--- 50,56 ----
  		   char storage,
  		   int32 typeMod,
  		   int32 typNDims,
!  		   bool typeNotNull);
  
  extern void GenerateTypeDependencies(Oid typeNamespace,
  						 Oid typeObjectId,
*** a/src/include/commands/typecmds.h
--- b/src/include/commands/typecmds.h
***************
*** 24,29 **** extern void RemoveTypes(DropStmt *drop);
--- 24,30 ----
  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);
  
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
***************
*** 351,357 **** typedef enum NodeTag
  	T_DropUserMappingStmt,
  	T_AlterTableSpaceOptionsStmt,
  	T_SecLabelStmt,
! 
  	/*
  	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
  	 */
--- 351,357 ----
  	T_DropUserMappingStmt,
  	T_AlterTableSpaceOptionsStmt,
  	T_SecLabelStmt,
! 	T_AlterEnumStmt,
  	/*
  	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
  	 */
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 2195,2200 **** typedef struct CreateEnumStmt
--- 2195,2214 ----
  
  
  /* ----------------------
+  *		Alter Type Statement, enum types
+  * ----------------------
+  */
+ typedef struct AlterEnumStmt
+ {
+ 	NodeTag		type;
+ 	List	   *typeName;		/* qualified name (list of Value strings) */
+ 	char	   *newVal;			/* new enum value */
+ 	char	   *newValNeighbour;/* neighbouring enum value */
+ 	bool	    newValIsAfter;	/* new enum value is after neighbour? */
+ } AlterEnumStmt;
+ 
+ 
+ /* ----------------------
   *		Create View Statement
   * ----------------------
   */
*** a/src/test/regress/expected/enum.out
--- b/src/test/regress/expected/enum.out
***************
*** 25,30 **** ERROR:  invalid input value for enum rainbow: "mauve"
--- 25,92 ----
  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   |             1
+  venus     |             2
+  earth     |             3
+  mars      |             4
+  jupiter   |             5
+  saturn    |             6
+  uranus    |             7
+  neptune   |             8
+ (8 rows)
+ 
+ select 'mars'::planets > 'mercury' as using_sortorder;
+  using_sortorder 
+ -----------------
+  t
+ (1 row)
+ 
+ -- 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 label.
+ DROP TYPE planets;
+ --
  -- Basic table creation, row selection
  --
  CREATE TABLE enumtest (col rainbow);
***************
*** 403,409 **** 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 
! -----------+-----------
  (0 rows)
  
--- 465,471 ----
  
  SELECT * FROM pg_enum WHERE NOT EXISTS
    (SELECT 1 FROM pg_type WHERE pg_type.oid = enumtypid);
!  enumtypid | enumlabel | enumsortorder 
! -----------+-----------+---------------
  (0 rows)
  
*** a/src/test/regress/sql/enum.sql
--- b/src/test/regress/sql/enum.sql
***************
*** 16,21 **** SELECT 'red'::rainbow;
--- 16,61 ----
  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 'mars'::planets > 'mercury' as using_sortorder;
+ 
+ -- errors for adding labels 
+ ALTER TYPE planets ADD 
+ 	  'plutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutopluto';
+ 
+ ALTER TYPE planets ADD 'pluto' AFTER 'zeus';
+ 
+ DROP TYPE planets;
+ --
  -- Basic table creation, row selection
  --
  CREATE TABLE enumtest (col rainbow);
#76Robert Haas
robertmhaas@gmail.com
In reply to: Andrew Dunstan (#75)
Re: WIP: extensible enums

On Tue, Oct 19, 2010 at 5:42 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

Well a bit more testing shows some benefit. I've sorted out a few kinks, so
this seems to work. In particular, with the above tables, the version
imported from 9.0 can create have an index created in about the same time as
on the fresh table (identical data, but all even numbered Oids).

Of course, with lots of odd numbered Oids, if a label gets added the
imported version will degrade in performance much more quickly.

I'm quite impressed by the amount of time and thought being put into
optimizing this. I didn't realize people cared so much about enum
performance; but it's good that they do.

I hope to see more such efforts in other parts of the system.

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

#77Andrew Dunstan
andrew@dunslane.net
In reply to: Robert Haas (#76)
Re: WIP: extensible enums

On 10/19/2010 08:51 PM, Robert Haas wrote:

On Tue, Oct 19, 2010 at 5:42 PM, Andrew Dunstan<andrew@dunslane.net> wrote:

Well a bit more testing shows some benefit. I've sorted out a few kinks, so
this seems to work. In particular, with the above tables, the version
imported from 9.0 can create have an index created in about the same time as
on the fresh table (identical data, but all even numbered Oids).

Of course, with lots of odd numbered Oids, if a label gets added the
imported version will degrade in performance much more quickly.

I'm quite impressed by the amount of time and thought being put into
optimizing this. I didn't realize people cared so much about enum
performance; but it's good that they do.

I hope to see more such efforts in other parts of the system.

:-)

Efficiency has always been one of the major reasons for using enums, so
it's important that we make them extensible without badly affecting
performance.

cheers

andrew

#78David Fetter
david@fetter.org
In reply to: Robert Haas (#76)
Re: WIP: extensible enums

On Tue, Oct 19, 2010 at 08:51:16PM -0400, Robert Haas wrote:

On Tue, Oct 19, 2010 at 5:42 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

Well a bit more testing shows some benefit. I've sorted out a few kinks, so
this seems to work. In particular, with the above tables, the version
imported from 9.0 can create have an index created in about the same time as
on the fresh table (identical data, but all even numbered Oids).

Of course, with lots of odd numbered Oids, if a label gets added the
imported version will degrade in performance much more quickly.

I'm quite impressed by the amount of time and thought being put into
optimizing this. I didn't realize people cared so much about enum
performance; but it's good that they do.

I hope to see more such efforts in other parts of the system.

Which parts of the system, in particular, do you have in mind? Other
people from EDB have mentioned that slimming down the on-disk
representation was one such target. What other ones would you see as
needing such attention?

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#79Robert Haas
robertmhaas@gmail.com
In reply to: David Fetter (#78)
Re: WIP: extensible enums

On Wed, Oct 20, 2010 at 3:16 PM, David Fetter <david@fetter.org> wrote:

On Tue, Oct 19, 2010 at 08:51:16PM -0400, Robert Haas wrote:

On Tue, Oct 19, 2010 at 5:42 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

Well a bit more testing shows some benefit. I've sorted out a few kinks, so
this seems to work. In particular, with the above tables, the version
imported from 9.0 can create have an index created in about the same time as
on the fresh table (identical data, but all even numbered Oids).

Of course, with lots of odd numbered Oids, if a label gets added the
imported version will degrade in performance much more quickly.

I'm quite impressed by the amount of time and thought being put into
optimizing this.  I didn't realize people cared so much about enum
performance; but it's good that they do.

I hope to see more such efforts in other parts of the system.

Which parts of the system, in particular, do you have in mind?  Other
people from EDB have mentioned that slimming down the on-disk
representation was one such target.  What other ones would you see as
needing such attention?

On-disk footprint.
WAL volume.
COPY speed.
Checkpoint I/O.

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

#80Merlin Moncure
mmoncure@gmail.com
In reply to: Andrew Dunstan (#77)
Re: WIP: extensible enums

On Tue, Oct 19, 2010 at 9:15 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

Efficiency has  always been one of the major reasons for using enums, so
it's important that we make them extensible without badly affecting
performance.

on that note is it worthwhile backpatching recent versions to allocate
enums with even numbered oids? That way people binary upgrading can
get the benefit of the optimization they should qualify for...

merlin

#81Robert Haas
robertmhaas@gmail.com
In reply to: Merlin Moncure (#80)
Re: WIP: extensible enums

On Wed, Oct 20, 2010 at 6:54 PM, Merlin Moncure <mmoncure@gmail.com> wrote:

On Tue, Oct 19, 2010 at 9:15 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

Efficiency has  always been one of the major reasons for using enums, so
it's important that we make them extensible without badly affecting
performance.

on that note is it worthwhile backpatching recent versions to allocate
enums with even numbered oids? That way people binary upgrading can
get the benefit of the optimization they should qualify for...

Uh, -1 from me. This is not a bug fix, and it will only help people
who create new enums between the time they upgrade to the relevant
minor release and the time they upgrade to 9.1. We are not into the
business of back-patching marginal peformance enhancements. If we
want to have a 9.0R2 release, or whatever, then so be it, but let's
not be modifying behavior in stable branches unless there's a *bug*.

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

#82Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#75)
Re: WIP: extensible enums

Andrew Dunstan <andrew@dunslane.net> writes:

Latest patch attached.

I've been working through this patch. It occurs to me that there's a
fairly serious problem with the current implementation of insertion of
new values within the bounds of the current sort ordering. Namely, that
it does that by reassigning the enumsortorder values of pre-existing
rows. That creates a race condition: suppose that our backend is doing
that while another process is busy loading its internal cache of values
of the enum. If we commit partway through the other process's loading
of its cache, it may end up with a cache containing some pre-commit
entries and some post-commit entries. In the worst case it might even
have two images of the same enum label, with different enumsortorder
values. Needless to say, this is catastrophic for correctness of
subsequent comparisons in the other process.

We could try to avoid the race condition, but it's not going to be easy.
I think a better idea is to avoid having to issue updates against
pg_enum rows once they are inserted. To do that, I propose that instead
of integer enumsortorder values, we use float8 values. The initial
entries for a type would still have numbers 1..n, but when we need to
insert a value between two existing entries, we assign it a value
halfway between their enumsortorder values. Then we never have to alter
pre-existing entries, and there's no race condition: at worst, a
process's cache entry might be missing some just-committed rows, and we
know how to deal with that.

The disadvantage of this scheme is that if you repeatedly insert entries
in the "same place" in the sort order, you halve the available range
each time, so you'd run out of room after order-of-fifty halvings.
The values would eventually differ by only one unit in the last place,
so it'd not be possible to insert another value that would still be
distinguishable in the sort order. Is that an acceptable restriction?
(If you did run into this, you could manually reassign enumsortorder
values to get out of it, without having to dump-and-reload; but you'd
have to beware of the same race condition as above.) Of course adding
new values at the start or end of the enum's list wouldn't have that
restriction.

Thoughts?

regards, tom lane

#83Josh Berkus
josh@agliodbs.com
In reply to: Tom Lane (#82)
Re: WIP: extensible enums

The disadvantage of this scheme is that if you repeatedly insert entries
in the "same place" in the sort order, you halve the available range
each time, so you'd run out of room after order-of-fifty halvings.

This is not a real issue. If anyone is using an ENUM with 1000 values
in it, they're doing it wrong. However, we'd have to present an
intelligible error message in that case.

The float method would also have a couple other issues:

(1) larger value for the enum order, so more RAM. Do we care?
(2) would need to create a view which hid the floats from admins who
just want to look at the enum ordering.

--
-- Josh Berkus
PostgreSQL Experts Inc.
http://www.pgexperts.com

#84Tom Lane
tgl@sss.pgh.pa.us
In reply to: Josh Berkus (#83)
Re: WIP: extensible enums

Josh Berkus <josh@agliodbs.com> writes:

The disadvantage of this scheme is that if you repeatedly insert entries
in the "same place" in the sort order, you halve the available range
each time, so you'd run out of room after order-of-fifty halvings.

This is not a real issue. If anyone is using an ENUM with 1000 values
in it, they're doing it wrong. However, we'd have to present an
intelligible error message in that case.

You wouldn't need 1000 values to get into trouble. If you did

CREATE TYPE e AS ENUM ('a', 'b');

ALTER TYPE e ADD 'c1' BEFORE 'b';
ALTER TYPE e ADD 'c2' BEFORE 'b';
ALTER TYPE e ADD 'c3' BEFORE 'b';
ALTER TYPE e ADD 'c4' BEFORE 'b';
...

you'd hit the failure somewhere around c50, assuming IEEE-style floats.
I think an "intelligible error message" wouldn't be hard; it'd say
something like "no more room to insert another enum label between enum
labels c49 and b".

The float method would also have a couple other issues:

(1) larger value for the enum order, so more RAM. Do we care?

The in-memory representation wouldn't be any bigger, because we don't
actually need to keep the enumorder values in the cache entries.
pg_enum rows would get wider, but not materially so considering they
already contain a 64-byte name column.

(2) would need to create a view which hid the floats from admins who
just want to look at the enum ordering.

Why? We weren't going to hide the enumorder values before, why would we
do it with this?

regards, tom lane

#85Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#82)
Re: WIP: extensible enums

On Oct 23, 2010, at 7:12 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Andrew Dunstan <andrew@dunslane.net> writes:

Latest patch attached.

I've been working through this patch. It occurs to me that there's a
fairly serious problem with the current implementation of insertion of
new values within the bounds of the current sort ordering. Namely, that
it does that by reassigning the enumsortorder values of pre-existing
rows. That creates a race condition:

It strikes me that this is merely one facet of our failure to do proper locking on DDL objects other than relations, and that this would be as good a time as any to start fixing it. ISTM that ALTER TYPE should grab a self-excluding lock just as ALTER TABLE already does.

...Robert

#86Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#85)
Re: WIP: extensible enums

Robert Haas <robertmhaas@gmail.com> writes:

On Oct 23, 2010, at 7:12 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I've been working through this patch. It occurs to me that there's a
fairly serious problem with the current implementation of insertion of
new values within the bounds of the current sort ordering. Namely, that
it does that by reassigning the enumsortorder values of pre-existing
rows. That creates a race condition:

It strikes me that this is merely one facet of our failure to do proper locking on DDL objects other than relations, and that this would be as good a time as any to start fixing it. ISTM that ALTER TYPE should grab a self-excluding lock just as ALTER TABLE already does.

The point of all the design thrashing we've been doing here is to
*avoid* taking locks while comparing enum OIDs. So I'm not impressed
with this proposal. (A self-exclusive lock to prevent concurrent
ALTER TYPEs might be a good idea, but I don't want to block enum
comparisons too.)

I did just think of a possible solution that would work with the
updating implementation: readers of pg_enum could use an MVCC snapshot
instead of SnapshotNow while loading their caches. I'm not certain
offhand how unpleasant this'd be at the C-code level, but it should
be possible. I still prefer the idea of not changing rows once they're
inserted, though --- doing so could possibly also cause transient
failures in, eg, enum_in/enum_out, since those depend on syscaches that
are loaded with SnapshotNow.

regards, tom lane

#87Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#86)
Re: WIP: extensible enums

On Oct 23, 2010, at 7:52 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I still prefer the idea of not changing rows once they're
inserted, though

Me too. But I really dislike the idea of having a failure mode where we can't insert for no reason that the user can understand. So I'm trying to think of a better option.

Why would you need to lock out type comparisons? Locking out concurrent DDL seems sufficient.

...Robert

#88Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#87)
Re: WIP: extensible enums

Robert Haas <robertmhaas@gmail.com> writes:

Why would you need to lock out type comparisons?

Didn't you get the point? The hazard is to a concurrent process that is
merely trying to load up its enum-values cache so that it can perform an
enum comparison. I don't want such an operation to have to block,
especially not against something that's trying to acquire a more or less
exclusive lock.

regards, tom lane

#89Andrew Dunstan
andrew@dunslane.net
In reply to: Tom Lane (#82)
Re: WIP: extensible enums

On 10/23/2010 07:12 PM, Tom Lane wrote:

Andrew Dunstan<andrew@dunslane.net> writes:

Latest patch attached.

I've been working through this patch.

Cool.

[snip: reallocating enum sortorder on existing values has race
conditions. Suggestion to use a float8 instead and add new value half
way between neighbours, so no reassignment is necessary]

The disadvantage of this scheme is that if you repeatedly insert entries
in the "same place" in the sort order, you halve the available range
each time, so you'd run out of room after order-of-fifty halvings.
The values would eventually differ by only one unit in the last place,
so it'd not be possible to insert another value that would still be
distinguishable in the sort order. Is that an acceptable restriction?
(If you did run into this, you could manually reassign enumsortorder
values to get out of it, without having to dump-and-reload; but you'd
have to beware of the same race condition as above.) Of course adding
new values at the start or end of the enum's list wouldn't have that
restriction.

Well, it's a tiny bit like my initial proposal for enum Oid ranges with
gaps, but with a level of indirection :-)

Seriously, I think it might be OK. Could we provide some safe way of
resetting the sortorder values? Or even a not-entirely-safe
superuser-only function might be useful. Binary upgrade could probably
call it safely, for example.

We've sure expended quite a lot of neurons on this feature :-)

cheers

andrew

#90Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#89)
Re: WIP: extensible enums

Andrew Dunstan <andrew@dunslane.net> writes:

Seriously, I think it might be OK. Could we provide some safe way of
resetting the sortorder values? Or even a not-entirely-safe
superuser-only function might be useful. Binary upgrade could probably
call it safely, for example.

You could do it with plain SQL, as long as you weren't concerned about
confusing processes that were concurrently loading their enum caches.

Another thought here is that the split-in-half rule might be
unnecessarily dumb. It leaves equal amounts of code space on both sides
of the new value, even though the odds of subsequent insertions on both
sides are probably unequal. But I'm not sure if we can predict the
usage pattern well enough to know which side is more likely.

regards, tom lane

#91Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#88)
Re: WIP: extensible enums

On Sat, Oct 23, 2010 at 8:11 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

Why would you need to lock out type comparisons?

Didn't you get the point?  The hazard is to a concurrent process that is
merely trying to load up its enum-values cache so that it can perform an
enum comparison.  I don't want such an operation to have to block,
especially not against something that's trying to acquire a more or less
exclusive lock.

Hmm, yeah, I missed the point. Sorry.

I suppose you could fix this by always updating every row, and storing
in each row the total count of elements (or a random number). Then
it'd be obvious if you'd read an inconsistent view of the world.

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

#92Andrew Dunstan
andrew@dunslane.net
In reply to: Tom Lane (#90)
Re: WIP: extensible enums

On 10/23/2010 08:54 PM, Tom Lane wrote:

Another thought here is that the split-in-half rule might be
unnecessarily dumb. It leaves equal amounts of code space on both sides
of the new value, even though the odds of subsequent insertions on both
sides are probably unequal. But I'm not sure if we can predict the
usage pattern well enough to know which side is more likely.

We can't. In particular, we can't rely on the label to tell us, so we
have no information at all to go on, really. Let's just go with the
simple midpoint.

Are you going to try doing this?

cheers

andrew

#93Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#91)
Re: WIP: extensible enums

Robert Haas <robertmhaas@gmail.com> writes:

I suppose you could fix this by always updating every row, and storing
in each row the total count of elements (or a random number). Then
it'd be obvious if you'd read an inconsistent view of the world.

Well, the easy way to read a consistent view of the world is to load the
cache using an MVCC snapshot instead of SnapshotNow. The current code
structure isn't amenable to that because it's relying on a syscache to
fetch the data for it, but that seems pretty inefficient anyway. I'm
thinking of changing it around so that the enum cache gets loaded with
a regular systable_beginscan() scan, and then we could load with an
MVCC snapshot.

I'm kind of inclined to go to the float-based representation anyway,
though, just because not having to update other rows to do an insert
seems like a good thing. But we could combine that with an MVCC
snapshot on the read side, which would make renumbering safe, which
would mean that we could auto-renumber when we ran out of code space
and not otherwise. Is that getting too complicated?

regards, tom lane

#94Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#93)
Re: WIP: extensible enums

On Sun, Oct 24, 2010 at 12:26 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

I suppose you could fix this by always updating every row, and storing
in each row the total count of elements (or a random number).  Then
it'd be obvious if you'd read an inconsistent view of the world.

Well, the easy way to read a consistent view of the world is to load the
cache using an MVCC snapshot instead of SnapshotNow.

Yeah, I have to admit I had never given much thought to how evil
SnapshotNow is. I wonder if we have latent bugs because of this.
Seems like anything where an object is represented by more than one
table row is suspect.

The current code
structure isn't amenable to that because it's relying on a syscache to
fetch the data for it, but that seems pretty inefficient anyway.  I'm
thinking of changing it around so that the enum cache gets loaded with
a regular systable_beginscan() scan, and then we could load with an
MVCC snapshot.

That sounds reasonable.

I'm kind of inclined to go to the float-based representation anyway,
though, just because not having to update other rows to do an insert
seems like a good thing.  But we could combine that with an MVCC
snapshot on the read side, which would make renumbering safe, which
would mean that we could auto-renumber when we ran out of code space
and not otherwise.  Is that getting too complicated?

The complexity doesn't bother me, but I'd probably just renumber every
time. It's worth optimizing enums for speed, but micro-optimizing the
enum DDL for speed may not be worth it, especially because it will
ensure that the renumbering code is very rarely tested.

If you are going to only renumber when necessary, I'd probably make
the ordering position an integer rather than a float, and just set the
values for the initial labels to something like 256 * 10^16, 257 *
10^16, 258 * 10^16, 259 * 10^16; and then some regression tests that
add labels a, z, y, x, .... I dunno if floats have completely
consistent representations on all the platforms we support, but with
integers it should be quite easy to predict the exact point when
you're going to run out of space and what the contents of pg_enum
should be just before and just after that point.

--
Robert Haas
President, Floating-Point Haters Anonymous

#95Greg Stark
gsstark@mit.edu
In reply to: Robert Haas (#94)
Re: WIP: extensible enums

On Sat, Oct 23, 2010 at 10:11 PM, Robert Haas <robertmhaas@gmail.com> wrote:

I dunno if floats have completely
consistent representations on all the platforms we support, but with
integers it should be quite easy to predict the exact point when
you're going to run out of space and what the contents of pg_enum
should be just before and just after that point.

There's nothing magic about the integral types here. If you use a
string then you could always split by making the string longer. It
would get weird after you get to 256 (you can still handle it but
there would be weird special case code) but an array of integers would
be just as flexible and wouldn't have the same problem.

--
greg

#96Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Tom Lane (#93)
Re: WIP: extensible enums

On 24 October 2010 05:26, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Well, the easy way to read a consistent view of the world is to load the
cache using an MVCC snapshot instead of SnapshotNow.  The current code
structure isn't amenable to that because it's relying on a syscache to
fetch the data for it, but that seems pretty inefficient anyway.  I'm
thinking of changing it around so that the enum cache gets loaded with
a regular systable_beginscan() scan, and then we could load with an
MVCC snapshot.

I'm kind of inclined to go to the float-based representation anyway,
though, just because not having to update other rows to do an insert
seems like a good thing.  But we could combine that with an MVCC
snapshot on the read side, which would make renumbering safe, which
would mean that we could auto-renumber when we ran out of code space
and not otherwise.  Is that getting too complicated?

As an alternative, how about storing the sort order as an array of
OIDs, in a single row in pg_type, or a new table, rather than across
multiple rows in pg_enum.

Code that read it would be guaranteed a consistent view of the data,
and at worst, it might be missing recently added elements, which the
comparison code can already deal with by re-reading the array.

Regards,
Dean

Show quoted text

                       regards, tom lane

#97Tom Lane
tgl@sss.pgh.pa.us
In reply to: Greg Stark (#95)
Re: WIP: extensible enums

Greg Stark <gsstark@mit.edu> writes:

There's nothing magic about the integral types here. If you use a
string then you could always split by making the string longer.

The entire objective here is to make enum comparisons fast. Somehow,
going to a non-primitive data type to represent the sort values does
not seem like a win from that perspective.

(Likewise for Dean's suggestion for an array of another kind.)

What I'm currently thinking is that we should actually use float4 not
float8. That eliminates any concerns about making the representation
larger than it was before. We'll definitely have to have the logic
to do renumbering, but it'll still be an exceedingly rare thing in
practice. With float4 the implementation would fail at somewhere
around 2^24 elements in an enum (since even with renumbering, there
wouldn't be enough bits to give each element a distinguishable value).
I don't see that as a real objection, and anyway if you were trying
to have an enum with many elements, you'd want the in-memory
representation to be compact.

regards, tom lane

#98Andrew Dunstan
andrew@dunslane.net
In reply to: Tom Lane (#97)
Re: WIP: extensible enums

On 10/24/2010 12:20 PM, Tom Lane wrote:

With float4 the implementation would fail at somewhere
around 2^24 elements in an enum (since even with renumbering, there
wouldn't be enough bits to give each element a distinguishable value).
I don't see that as a real objection, and anyway if you were trying
to have an enum with many elements, you'd want the in-memory
representation to be compact.

Anything beyond the square root of this is getting pretty insane,
IMNSHO, so I'm really not that bothered by that number.

Assuming we renumber the sortorder as even positive integers, that
number comes down a couple of bits, but even so that gives us lots of
head room I think.

cheers

andrew

#99Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Tom Lane (#97)
Re: WIP: extensible enums

On 24 October 2010 17:20, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Greg Stark <gsstark@mit.edu> writes:

There's nothing magic about the integral types here. If you use a
string then you could always split by making the string longer.

The entire objective here is to make enum comparisons fast.  Somehow,
going to a non-primitive data type to represent the sort values does
not seem like a win from that perspective.

(Likewise for Dean's suggestion for an array of another kind.)

The point with an OID array is that you wouldn't need to store the
enumsortorder values at all. The sort order would just be the index of
the OID in the array. So the comparison code would read the OID array,
traverse it building an array of enum_sort structs {oid, idx}, sort
that by OID and cache it. Then do binary searches to efficiently find
the index (i.e., sort order) for any given OID. That's pretty much
what the comparison code is doing now, except without explicitly
stored sort positions.

The reason I thought this might help is that it would be a single
atomic operation to read the whole enum sort order, so you'd never get
a mix of old and new sort positions, if another transaction was
altering the enum.

What I'm currently thinking is that we should actually use float4 not
float8.  That eliminates any concerns about making the representation
larger than it was before.  We'll definitely have to have the logic
to do renumbering, but it'll still be an exceedingly rare thing in
practice.  With float4 the implementation would fail at somewhere
around 2^24 elements in an enum (since even with renumbering, there
wouldn't be enough bits to give each element a distinguishable value).
I don't see that as a real objection, and anyway if you were trying
to have an enum with many elements, you'd want the in-memory
representation to be compact.

That seems like a lot of additional complexity to do the renumbering,
and you'd have to make the comparison code safe against a concurrent
renumbering.

Regards,
Dean

Show quoted text

                       regards, tom lane

#100Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#98)
Re: WIP: extensible enums

Andrew Dunstan <andrew@dunslane.net> writes:

On 10/24/2010 12:20 PM, Tom Lane wrote:

With float4 the implementation would fail at somewhere
around 2^24 elements in an enum (since even with renumbering, there
wouldn't be enough bits to give each element a distinguishable value).
I don't see that as a real objection, and anyway if you were trying
to have an enum with many elements, you'd want the in-memory
representation to be compact.

Anything beyond the square root of this is getting pretty insane,
IMNSHO, so I'm really not that bothered by that number.

Here's a WIP patch that incorporates most of what's been discussed here.
The critical part of it is summed up in the comments for RenumberEnumType:

/*
* 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.
*/

This patch isn't committable as-is because I haven't made enum_first,
enum_last, enum_range follow these coding rules: they need to stop
using the syscache and instead use an indexscan on
pg_enum_typid_sortorder_index to locate the relevant rows. That should
be just a small fix though, and it seems likely to be a net win for
performance anyway. There are a couple of other loose ends
too, in particular I still think we need to prevent ALTER TYPE ADD
inside a transaction block because of the risk of finding undefined
enum OIDs in indexes.

Anybody really unhappy with this approach? If not, I'll finish this
up and commit it.

regards, tom lane

#101Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dean Rasheed (#99)
Re: WIP: extensible enums

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

The point with an OID array is that you wouldn't need to store the
enumsortorder values at all. The sort order would just be the index of
the OID in the array. So the comparison code would read the OID array,
traverse it building an array of enum_sort structs {oid, idx}, sort
that by OID and cache it.

Hmm. But I guess we'd have to keep that array in the pg_type row,
and it'd be a huge PITA to work with at the SQL level. For instance,
psql and pg_dump can easily be adapted to use enumsortorder instead
of pg_enum.oid when they want to read out the labels in sorted order.
Doing the same with an array representation would be a very different
and much uglier query. I'm not eager to contort the catalog
representation that much.

regards, tom lane

#102Andrew Dunstan
andrew@dunslane.net
In reply to: Tom Lane (#101)
Re: WIP: extensible enums

On 10/24/2010 03:33 PM, Tom Lane wrote:

Dean Rasheed<dean.a.rasheed@gmail.com> writes:

The point with an OID array is that you wouldn't need to store the
enumsortorder values at all. The sort order would just be the index of
the OID in the array. So the comparison code would read the OID array,
traverse it building an array of enum_sort structs {oid, idx}, sort
that by OID and cache it.

Hmm. But I guess we'd have to keep that array in the pg_type row,
and it'd be a huge PITA to work with at the SQL level. For instance,
psql and pg_dump can easily be adapted to use enumsortorder instead
of pg_enum.oid when they want to read out the labels in sorted order.
Doing the same with an array representation would be a very different
and much uglier query. I'm not eager to contort the catalog
representation that much.

If that's the only objection I don't know that it's terribly serious.
psql and pg_dump could sanely use something like:

select enum_oid, row_number() over () as sort_order
from unnest(null::myenum) as enum_oid

That said, I'm generally wary of array fields, especially in the catalog.

cheers

andrew

#103Andrew Dunstan
andrew@dunslane.net
In reply to: Tom Lane (#100)
Re: WIP: extensible enums

On 10/24/2010 03:28 PM, Tom Lane wrote:

This patch isn't committable as-is because I haven't made enum_first,
enum_last, enum_range follow these coding rules: they need to stop
using the syscache and instead use an indexscan on
pg_enum_typid_sortorder_index to locate the relevant rows. That should
be just a small fix though, and it seems likely to be a net win for
performance anyway. There are a couple of other loose ends too, in
particular I still think we need to prevent ALTER TYPE ADD inside a
transaction block because of the risk of finding undefined enum OIDs
in indexes. Anybody really unhappy with this approach? If not, I'll
finish this up and commit it.

I'll look at it tonight. At first glance it looks OK.

(BTW, this would be a good case for publishing your development branch
somewhere (e.g. git.postgresql.org). That way I could import it and
easily do a diff between your patch and mine, in about three simple
commands. There are other ways of getting at it, and I'll go and do
that, but we should start to look at using the nice facilities git
provides.)

cheers

andrew

#104Andrew Dunstan
andrew@dunslane.net
In reply to: Andrew Dunstan (#102)
Re: WIP: extensible enums

On 10/24/2010 05:09 PM, Andrew Dunstan wrote:

select enum_oid, row_number() over () as sort_order
from unnest(null::myenum) as enum_oid

Of course, I meant:

select enum_label, row_number() over () as sort_order
from unnest(enum_range(null::myenum)) as enum_label

cheers

andrew

#105Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#102)
Re: WIP: extensible enums

BTW, I've forgotten --- did anyone publish a test case for checking
performance of enum comparisons? Or should I just cons up something
privately?

regards, tom lane

#106Andrew Dunstan
andrew@dunslane.net
In reply to: Tom Lane (#105)
Re: WIP: extensible enums

On 10/24/2010 05:34 PM, Tom Lane wrote:

BTW, I've forgotten --- did anyone publish a test case for checking
performance of enum comparisons? Or should I just cons up something
privately?

I have been running tests with
<http://developer.postgresql.org/~adunstan/enumtest.dmp&gt;

The table "mydata" contains 100k rows with the column "label" containing
random values of an enum type with 500 labels. Basically, my main test
is to set up an index on that column. The alter the enum type, and test
again. Then alter some of the rows, and test again.

cheers

andrew

#107Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#106)
Re: WIP: extensible enums

Andrew Dunstan <andrew@dunslane.net> writes:

On 10/24/2010 05:34 PM, Tom Lane wrote:

BTW, I've forgotten --- did anyone publish a test case for checking
performance of enum comparisons? Or should I just cons up something
privately?

I have been running tests with
<http://developer.postgresql.org/~adunstan/enumtest.dmp&gt;

The table "mydata" contains 100k rows with the column "label" containing
random values of an enum type with 500 labels. Basically, my main test
is to set up an index on that column. The alter the enum type, and test
again. Then alter some of the rows, and test again.

OK, I did some timing consisting of building a btree index with
maintenance_work_mem set reasonably high. This is on a debug-enabled
build, so it's not representative of production performance, but it will
do for seeing what we're doing to enum comparison performance. Here's
what I tried:

Stock 9.0.1 24.9 sec

patch, all OIDs even 25.2 sec (~ 1% hit)

patch, half of OIDs odd 27.2 sec (~ 9% hit)

same, bitmapset forced null 64.9 sec (~ 160% hit)

(Note that the noise level in these measurements is about 1%;
I'm not entirely convinced that the all-even case is really measurably
slower than 9.0.)

The "half of OIDs odd" case is what you'd get for a binary upgrade
from a 9.0 database. The last case shows what happens if the
intermediate bitmapset-test optimization is disabled, forcing all
comparisons to do binary searches in the sorted-by-OID array
(except for the one-quarter of cases where both OIDs are even
by chance). It's pretty grim but it represents a worst case that
you'd be very unlikely to hit in practice.

This shows that the bitmapset optimization really is quite effective,
at least for cases where all the enum labels are sorted by OID after
all. That motivated me to change the bitmapset setup code to what's
attached. This is potentially a little slower at initializing the
cache, but it makes up for that by still marking most enum members
as sorted even when a few out-of-order members have been inserted.
The key point is that an out-of-order member in the middle of the
array doesn't prevent us from considering following members as
properly sorted, as long as they are correctly ordered with respect to
the other properly-sorted members.

With this approach we can honestly say that inserting an out-of-order
enum value doesn't impact comparison performance for pre-existing
enum members, only for comparisons involving the out-of-order value
itself; even when the existing members were binary-upgraded and thus
weren't all even. I think that's a worthwhile advantage.

IMHO this level of performance is good enough. Anyone unhappy?

regards, tom lane

/*
* 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 >= 1024)
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;
}

#108Andrew Dunstan
andrew@dunslane.net
In reply to: Tom Lane (#107)
Re: WIP: extensible enums

On 10/24/2010 08:12 PM, Tom Lane wrote:

OK, I did some timing consisting of building a btree index with
maintenance_work_mem set reasonably high. This is on a debug-enabled
build, so it's not representative of production performance, but it will
do for seeing what we're doing to enum comparison performance. Here's
what I tried:

Stock 9.0.1 24.9 sec

patch, all OIDs even 25.2 sec (~ 1% hit)

patch, half of OIDs odd 27.2 sec (~ 9% hit)

same, bitmapset forced null 64.9 sec (~ 160% hit)

(Note that the noise level in these measurements is about 1%;
I'm not entirely convinced that the all-even case is really measurably
slower than 9.0.)

Yeah, that was my conclusion. I tested with debug/cassert turned off,
but my results were similar.

The "half of OIDs odd" case is what you'd get for a binary upgrade
from a 9.0 database. The last case shows what happens if the
intermediate bitmapset-test optimization is disabled, forcing all
comparisons to do binary searches in the sorted-by-OID array
(except for the one-quarter of cases where both OIDs are even
by chance). It's pretty grim but it represents a worst case that
you'd be very unlikely to hit in practice.

This shows that the bitmapset optimization really is quite effective,
at least for cases where all the enum labels are sorted by OID after
all. That motivated me to change the bitmapset setup code to what's
attached. This is potentially a little slower at initializing the
cache, but it makes up for that by still marking most enum members
as sorted even when a few out-of-order members have been inserted.
The key point is that an out-of-order member in the middle of the
array doesn't prevent us from considering following members as
properly sorted, as long as they are correctly ordered with respect to
the other properly-sorted members.

That's nice. It's a tradeoff though. Bumping up the cost of setting up
the cache won't have much effect on a creating a large index, but could
affect to performance of retail comparisons significantly. But this is
probably worth it. You'd have to work hard to create the perverse case
that could result in seriously worse cache setup cost.

With this approach we can honestly say that inserting an out-of-order
enum value doesn't impact comparison performance for pre-existing
enum members, only for comparisons involving the out-of-order value
itself; even when the existing members were binary-upgraded and thus
weren't all even. I think that's a worthwhile advantage.

Yeah, that's nice.

IMHO this level of performance is good enough. Anyone unhappy?

No, seems good.

cheers

andrew

#109Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#108)
Re: WIP: extensible enums

Andrew Dunstan <andrew@dunslane.net> writes:

On 10/24/2010 08:12 PM, Tom Lane wrote:

This shows that the bitmapset optimization really is quite effective,
at least for cases where all the enum labels are sorted by OID after
all. That motivated me to change the bitmapset setup code to what's
attached. This is potentially a little slower at initializing the
cache, but it makes up for that by still marking most enum members
as sorted even when a few out-of-order members have been inserted.

That's nice. It's a tradeoff though. Bumping up the cost of setting up
the cache won't have much effect on a creating a large index, but could
affect to performance of retail comparisons significantly. But this is
probably worth it. You'd have to work hard to create the perverse case
that could result in seriously worse cache setup cost.

Well, notice that I moved the caching into typcache.c, rather than
having it be associated with query startup. So unless you're actively
frobbing the enum definition, that's going to be paid only once per
session.

regards, tom lane

#110Andrew Dunstan
andrew@dunslane.net
In reply to: Tom Lane (#109)
Re: WIP: extensible enums

On 10/24/2010 09:20 PM, Tom Lane wrote:

Andrew Dunstan<andrew@dunslane.net> writes:

On 10/24/2010 08:12 PM, Tom Lane wrote:

This shows that the bitmapset optimization really is quite effective,
at least for cases where all the enum labels are sorted by OID after
all. That motivated me to change the bitmapset setup code to what's
attached. This is potentially a little slower at initializing the
cache, but it makes up for that by still marking most enum members
as sorted even when a few out-of-order members have been inserted.

That's nice. It's a tradeoff though. Bumping up the cost of setting up
the cache won't have much effect on a creating a large index, but could
affect to performance of retail comparisons significantly. But this is
probably worth it. You'd have to work hard to create the perverse case
that could result in seriously worse cache setup cost.

Well, notice that I moved the caching into typcache.c, rather than
having it be associated with query startup. So unless you're actively
frobbing the enum definition, that's going to be paid only once per
session.

Oh, yes. Good. I'm just starting to look at this in detail.

cheers

andrew

#111Bruce Momjian
bruce@momjian.us
In reply to: Tom Lane (#109)
Re: WIP: extensible enums

Tom Lane wrote:

Andrew Dunstan <andrew@dunslane.net> writes:

On 10/24/2010 08:12 PM, Tom Lane wrote:

This shows that the bitmapset optimization really is quite effective,
at least for cases where all the enum labels are sorted by OID after
all. That motivated me to change the bitmapset setup code to what's
attached. This is potentially a little slower at initializing the
cache, but it makes up for that by still marking most enum members
as sorted even when a few out-of-order members have been inserted.

That's nice. It's a tradeoff though. Bumping up the cost of setting up
the cache won't have much effect on a creating a large index, but could
affect to performance of retail comparisons significantly. But this is
probably worth it. You'd have to work hard to create the perverse case
that could result in seriously worse cache setup cost.

Well, notice that I moved the caching into typcache.c, rather than
having it be associated with query startup. So unless you're actively
frobbing the enum definition, that's going to be paid only once per
session.

Thanks for modifying pg_upgrade so it works with this new format. The
changes look good and cleaner than what I had to do for 9.0.

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

+ It's impossible for everything to be true. +

#112Bruce Momjian
bruce@momjian.us
In reply to: Tom Lane (#109)
Re: WIP: extensible enums

Tom Lane wrote:

Andrew Dunstan <andrew@dunslane.net> writes:

On 10/24/2010 08:12 PM, Tom Lane wrote:

This shows that the bitmapset optimization really is quite effective,
at least for cases where all the enum labels are sorted by OID after
all. That motivated me to change the bitmapset setup code to what's
attached. This is potentially a little slower at initializing the
cache, but it makes up for that by still marking most enum members
as sorted even when a few out-of-order members have been inserted.

That's nice. It's a tradeoff though. Bumping up the cost of setting up
the cache won't have much effect on a creating a large index, but could
affect to performance of retail comparisons significantly. But this is
probably worth it. You'd have to work hard to create the perverse case
that could result in seriously worse cache setup cost.

Well, notice that I moved the caching into typcache.c, rather than
having it be associated with query startup. So unless you're actively
frobbing the enum definition, that's going to be paid only once per
session.

FYI, I marked the TODO item for adding enums as completed. The TODO
item used to also mention renaming or removing enums, but I have seen
few requests for that so I removed that suggestion. We can always
re-add it if there is demand.

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

+ It's impossible for everything to be true. +

#113Andrew Dunstan
andrew@dunslane.net
In reply to: Bruce Momjian (#112)
Re: WIP: extensible enums

On 11/12/2010 01:40 PM, Bruce Momjian wrote:

FYI, I marked the TODO item for adding enums as completed. The TODO
item used to also mention renaming or removing enums, but I have seen
few requests for that so I removed that suggestion. We can always
re-add it if there is demand.

Renaming an item would not be terribly hard. Removing one is that nasty
case. There are all sorts of places the old value could be referred to:
table data, view definitions, check constraints, functions etc.

cheers

andrew

#114Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#113)
Re: WIP: extensible enums

Andrew Dunstan <andrew@dunslane.net> writes:

On 11/12/2010 01:40 PM, Bruce Momjian wrote:

FYI, I marked the TODO item for adding enums as completed. The TODO
item used to also mention renaming or removing enums, but I have seen
few requests for that so I removed that suggestion. We can always
re-add it if there is demand.

Renaming an item would not be terribly hard. Removing one is that nasty
case. There are all sorts of places the old value could be referred to:
table data, view definitions, check constraints, functions etc.

Well, you can rename an item today if you don't mind doing a direct
UPDATE on pg_enum. I think that's probably sufficient if the demand
only amounts to one or two requests a year. I'd say leave it off the
TODO list till we see if there's more demand than that.

regards, tom lane

#115Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#114)
Re: WIP: extensible enums

On Fri, Nov 12, 2010 at 1:55 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Andrew Dunstan <andrew@dunslane.net> writes:

On 11/12/2010 01:40 PM, Bruce Momjian wrote:

FYI, I marked the TODO item for adding enums as completed.  The TODO
item used to also mention renaming or removing enums, but I have seen
few requests for that so I removed that suggestion.  We can always
re-add it if there is demand.

Renaming an item would not be terribly hard. Removing one is that nasty
case. There are all sorts of places the old value could be referred to:
table data, view definitions, check constraints, functions etc.

Well, you can rename an item today if you don't mind doing a direct
UPDATE on pg_enum.  I think that's probably sufficient if the demand
only amounts to one or two requests a year.  I'd say leave it off the
TODO list till we see if there's more demand than that.

I'd say put it on and mark it with an [E]. We could use some more
[E]asy items for that list.

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

#116Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#115)
Re: WIP: extensible enums

Robert Haas <robertmhaas@gmail.com> writes:

On Fri, Nov 12, 2010 at 1:55 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Well, you can rename an item today if you don't mind doing a direct
UPDATE on pg_enum. �I think that's probably sufficient if the demand
only amounts to one or two requests a year. �I'd say leave it off the
TODO list till we see if there's more demand than that.

I'd say put it on and mark it with an [E]. We could use some more
[E]asy items for that list.

We don't need to add marginally-useful features just because they're
easy. If it doesn't have a real use-case, the incremental maintenance
cost of more code is a good reason to reject it.

regards, tom lane

#117Alvaro Herrera
alvherre@commandprompt.com
In reply to: Bruce Momjian (#112)
Re: WIP: extensible enums

Excerpts from Bruce Momjian's message of vie nov 12 15:40:28 -0300 2010:

FYI, I marked the TODO item for adding enums as completed. The TODO
item used to also mention renaming or removing enums, but I have seen
few requests for that so I removed that suggestion. We can always
re-add it if there is demand.

I'm sure there's going to be more demand for ENUM features, now that
they are more usable.

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#118Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#116)
Re: WIP: extensible enums

On Nov 12, 2010, at 2:20 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Fri, Nov 12, 2010 at 1:55 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Well, you can rename an item today if you don't mind doing a direct
UPDATE on pg_enum. I think that's probably sufficient if the demand
only amounts to one or two requests a year. I'd say leave it off the
TODO list till we see if there's more demand than that.

I'd say put it on and mark it with an [E]. We could use some more
[E]asy items for that list.

We don't need to add marginally-useful features just because they're
easy. If it doesn't have a real use-case, the incremental maintenance
cost of more code is a good reason to reject it.

If we allow users to name objects, we ought to make every effort to also allow renaming them. In my mind, the only way renaming is too marginal to be useful is if the feature itself is too marginal to be useful.

...Robert

#119Joshua D. Drake
jd@commandprompt.com
In reply to: Tom Lane (#116)
Re: WIP: extensible enums

On Fri, 2010-11-12 at 14:20 -0500, Tom Lane wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Fri, Nov 12, 2010 at 1:55 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Well, you can rename an item today if you don't mind doing a direct
UPDATE on pg_enum. I think that's probably sufficient if the demand
only amounts to one or two requests a year. I'd say leave it off the
TODO list till we see if there's more demand than that.

I'd say put it on and mark it with an [E]. We could use some more
[E]asy items for that list.

We don't need to add marginally-useful features just because they're
easy. If it doesn't have a real use-case, the incremental maintenance
cost of more code is a good reason to reject it.

Perhaps we should remove the ability to rename tables and databases too.
It would certainly lighten the code path.

JD

regards, tom lane

--
PostgreSQL.org Major Contributor
Command Prompt, Inc: http://www.commandprompt.com/ - 509.416.6579
Consulting, Training, Support, Custom Development, Engineering
http://twitter.com/cmdpromptinc | http://identi.ca/commandprompt

#120Bruce Momjian
bruce@momjian.us
In reply to: Joshua D. Drake (#119)
Re: WIP: extensible enums

Joshua D. Drake wrote:

On Fri, 2010-11-12 at 14:20 -0500, Tom Lane wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Fri, Nov 12, 2010 at 1:55 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Well, you can rename an item today if you don't mind doing a direct
UPDATE on pg_enum. I think that's probably sufficient if the demand
only amounts to one or two requests a year. I'd say leave it off the
TODO list till we see if there's more demand than that.

I'd say put it on and mark it with an [E]. We could use some more
[E]asy items for that list.

We don't need to add marginally-useful features just because they're
easy. If it doesn't have a real use-case, the incremental maintenance
cost of more code is a good reason to reject it.

Perhaps we should remove the ability to rename tables and databases too.
It would certainly lighten the code path.

OK, got it. Added incomplete TODO item:

Allow renaming and deleting enumerated values from an existing
enumerated data type

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

+ It's impossible for everything to be true. +

#121Andrew Dunstan
andrew@dunslane.net
In reply to: Bruce Momjian (#120)
Re: WIP: extensible enums

On 11/12/2010 09:18 PM, Bruce Momjian wrote:

OK, got it. Added incomplete TODO item:

Allow renaming and deleting enumerated values from an existing
enumerated data type

I have serious doubts that deleting will ever be sanely doable.

cheers

andrew

#122Bruce Momjian
bruce@momjian.us
In reply to: Andrew Dunstan (#121)
Re: WIP: extensible enums

Andrew Dunstan wrote:

On 11/12/2010 09:18 PM, Bruce Momjian wrote:

OK, got it. Added incomplete TODO item:

Allow renaming and deleting enumerated values from an existing
enumerated data type

I have serious doubts that deleting will ever be sanely doable.

True. Should we not mention it then? I can't think of many objects we
can't delete.

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

+ It's impossible for everything to be true. +

#123Peter Eisentraut
peter_e@gmx.net
In reply to: Robert Haas (#118)
Re: WIP: extensible enums

On fre, 2010-11-12 at 17:19 -0500, Robert Haas wrote:

If we allow users to name objects, we ought to make every effort to
also allow renaming them. In my mind, the only way renaming is too
marginal to be useful is if the feature itself is too marginal to be
useful.

The bottom line is, any kind of database object needs to be changeable
and removable, otherwise there will always be hesitations about its use.
And when there are hesitations about the use, it's often easiest not to
bother.

I remember ten years ago or so we used to send people away who requested
the ability to drop columns, claiming they didn't plan their database
properly, or they should load it from scratch. Nowadays that is
ludicrous; databases live forever, development is agile, everything
needs to be changeable.

#124Robert Haas
robertmhaas@gmail.com
In reply to: Peter Eisentraut (#123)
Re: WIP: extensible enums

On Sat, Nov 13, 2010 at 12:30 PM, Peter Eisentraut <peter_e@gmx.net> wrote:

On fre, 2010-11-12 at 17:19 -0500, Robert Haas wrote:

If we allow users to name objects, we ought to make every effort to
also allow renaming them.  In my mind, the only way renaming is too
marginal to be useful is if the feature itself is too marginal to be
useful.

The bottom line is, any kind of database object needs to be changeable
and removable, otherwise there will always be hesitations about its use.
And when there are hesitations about the use, it's often easiest not to
bother.

I remember ten years ago or so we used to send people away who requested
the ability to drop columns, claiming they didn't plan their database
properly, or they should load it from scratch.  Nowadays that is
ludicrous; databases live forever, development is agile, everything
needs to be changeable.

It was ludicrous then, too. I picked MySQL for several projects early
on for precisely the lack of the ability to drop columns in
PostgreSQL.

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

#125Josh Berkus
josh@agliodbs.com
In reply to: Tom Lane (#116)
Re: WIP: extensible enums

I'd say put it on and mark it with an [E]. We could use some more
[E]asy items for that list.

We don't need to add marginally-useful features just because they're
easy. If it doesn't have a real use-case, the incremental maintenance
cost of more code is a good reason to reject it.

I'll bite.

Use-case:

1) DBA adds "Department Role" enum, with set
{'Director','Secretary','Staff','Support','Temporary','Liason'}.

2) 3-person data entry team updates all employee records with those roles.

3) First summary report is run.

4) Manager points out that "Liason" is misspelled.

--
-- Josh Berkus
PostgreSQL Experts Inc.
http://www.pgexperts.com