diff --git a/doc/src/sgml/ref/alter_type.sgml b/doc/src/sgml/ref/alter_type.sgml
index 67be1dd..0380f6c 100644
--- a/doc/src/sgml/ref/alter_type.sgml
+++ b/doc/src/sgml/ref/alter_type.sgml
@@ -30,6 +30,7 @@ ALTER TYPE <replaceable class="parameter">name</replaceable> RENAME TO <replacea
 ALTER TYPE <replaceable class="parameter">name</replaceable> SET SCHEMA <replaceable class="parameter">new_schema</replaceable>
 ALTER TYPE <replaceable class="parameter">name</replaceable> ADD VALUE [ IF NOT EXISTS ] <replaceable class="parameter">new_enum_value</replaceable> [ { BEFORE | AFTER } <replaceable class="parameter">neighbor_enum_value</replaceable> ]
 ALTER TYPE <replaceable class="parameter">name</replaceable> RENAME VALUE <replaceable class="parameter">existing_enum_value</replaceable> TO <replaceable class="parameter">new_enum_value</replaceable>
+ALTER TYPE <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">property</replaceable> = <replaceable class="parameter">value</replaceable> [, ... ] )
 
 <phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
 
@@ -70,7 +71,7 @@ ALTER TYPE <replaceable class="parameter">name</replaceable> RENAME VALUE <repla
    </varlistentry>
 
    <varlistentry>
-    <term><literal>SET DATA TYPE</literal></term>
+    <term><literal>ALTER ATTRIBUTE ... SET DATA TYPE</literal></term>
     <listitem>
      <para>
       This form changes the type of an attribute of a composite type.
@@ -135,6 +136,80 @@ ALTER TYPE <replaceable class="parameter">name</replaceable> RENAME VALUE <repla
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term>
+     <literal>SET ( <replaceable class="parameter">property</replaceable> = <replaceable class="parameter">value</replaceable> [, ... ] )</literal>
+    </term>
+    <listitem>
+     <para>
+      This form is only applicable to base types.  It allows adjustment of a
+      subset of the base-type properties that can be set in <command>CREATE
+      TYPE</command>.  Specifically, these properties can be changed:
+      <itemizedlist>
+       <listitem>
+        <para>
+         <literal>RECEIVE</literal> can be set to the name of a binary input
+         function, or <literal>NONE</literal> to remove the type's binary
+         input function.  Using this option requires superuser privilege.
+        </para>
+       </listitem>
+       <listitem>
+        <para>
+         <literal>SEND</literal> can be set to the name of a binary output
+         function, or <literal>NONE</literal> to remove the type's binary
+         output function.  Using this option requires superuser privilege.
+        </para>
+       </listitem>
+       <listitem>
+        <para>
+         <literal>TYPMOD_IN</literal> can be set to the name of a type
+         modifier input function, or <literal>NONE</literal> to remove the
+         type's type modifier input function.  Using this option requires
+         superuser privilege.
+        </para>
+       </listitem>
+       <listitem>
+        <para>
+         <literal>TYPMOD_OUT</literal> can be set to the name of a type
+         modifier output function, or <literal>NONE</literal> to remove the
+         type's type modifier output function.  Using this option requires
+         superuser privilege.
+        </para>
+       </listitem>
+       <listitem>
+        <para>
+         <literal>ANALYZE</literal> can be set to the name of a type-specific
+         statistics collection function, or <literal>NONE</literal> to remove
+         the type's statistics collection function.  Using this option
+         requires superuser privilege.
+        </para>
+       </listitem>
+       <listitem>
+        <para>
+         <literal>STORAGE</literal><indexterm>
+          <primary>TOAST</primary>
+          <secondary>per-type storage settings</secondary>
+         </indexterm>
+         can be set to <literal>plain</literal>,
+         <literal>extended</literal>, <literal>external</literal>,
+         or <literal>main</literal> (see <xref linkend="storage-toast"/> for
+         more information about what these mean).  However, changing
+         between <literal>plain</literal> and other settings is not allowed.
+         Note that changing this option doesn't by itself change any stored
+         data, it just sets the default TOAST strategy to be used for table
+         columns created in the future.  See <xref linkend="sql-altertable"/>
+         to change the TOAST strategy for existing table columns.
+        </para>
+       </listitem>
+      </itemizedlist>
+      See <xref linkend="sql-createtype"/> for more details about these
+      type properties.  Note that where appropriate, a change in these
+      properties for a base type will be propagated automatically to domains
+      based on that type.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
   </para>
 
@@ -156,7 +231,7 @@ ALTER TYPE <replaceable class="parameter">name</replaceable> RENAME VALUE <repla
    doesn't do anything you couldn't do by dropping and recreating the type.
    However, a superuser can alter ownership of any type anyway.)
    To add an attribute or alter an attribute type, you must also
-   have <literal>USAGE</literal> privilege on the data type.
+   have <literal>USAGE</literal> privilege on the attribute's data type.
   </para>
  </refsect1>
 
@@ -353,7 +428,20 @@ ALTER TYPE colors ADD VALUE 'orange' AFTER 'red';
    To rename an enum value:
 <programlisting>
 ALTER TYPE colors RENAME VALUE 'purple' TO 'mauve';
-</programlisting></para>
+</programlisting>
+  </para>
+
+  <para>
+   To create binary I/O functions for an existing base type:
+<programlisting>
+CREATE FUNCTION mytypesend(mytype) RETURNS bytea ...;
+CREATE FUNCTION mytyperecv(internal, oid, integer) RETURNS mytype ...;
+ALTER TYPE mytype SET (
+    send = mytypesend,
+    receive = mytyperecv
+);
+</programlisting>
+  </para>
  </refsect1>
 
  <refsect1>
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index 56e0bcf..cd56714 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -155,8 +155,8 @@ TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId)
 	 * Create dependencies.  We can/must skip this in bootstrap mode.
 	 */
 	if (!IsBootstrapProcessingMode())
-		GenerateTypeDependencies(typoid,
-								 (Form_pg_type) GETSTRUCT(tup),
+		GenerateTypeDependencies(tup,
+								 pg_type_desc,
 								 NULL,
 								 NULL,
 								 0,
@@ -488,8 +488,8 @@ TypeCreate(Oid newTypeOid,
 	 * Create dependencies.  We can/must skip this in bootstrap mode.
 	 */
 	if (!IsBootstrapProcessingMode())
-		GenerateTypeDependencies(typeObjectId,
-								 (Form_pg_type) GETSTRUCT(tup),
+		GenerateTypeDependencies(tup,
+								 pg_type_desc,
 								 (defaultTypeBin ?
 								  stringToNode(defaultTypeBin) :
 								  NULL),
@@ -516,12 +516,16 @@ TypeCreate(Oid newTypeOid,
  * GenerateTypeDependencies: build the dependencies needed for a type
  *
  * Most of what this function needs to know about the type is passed as the
- * new pg_type row, typeForm.  But we can't get at the varlena fields through
- * that, so defaultExpr and typacl are passed separately.  (typacl is really
+ * new pg_type row, typeTuple.  We make callers pass the pg_type Relation
+ * as well, so that we have easy access to a tuple descriptor for the row.
+ *
+ * While this is able to extract the defaultExpr and typacl from the tuple,
+ * doing so is relatively expensive, and callers may have those values at
+ * hand already.  Pass those if handy, otherwise pass NULL.  (typacl is really
  * "Acl *", but we declare it "void *" to avoid including acl.h in pg_type.h.)
  *
- * relationKind and isImplicitArray aren't visible in the pg_type row either,
- * so they're also passed separately.
+ * relationKind and isImplicitArray are likewise somewhat expensive to deduce
+ * from the tuple, so we make callers pass those (they're not optional).
  *
  * isDependentType is true if this is an implicit array or relation rowtype;
  * that means it doesn't need its own dependencies on owner etc.
@@ -535,8 +539,8 @@ TypeCreate(Oid newTypeOid,
  * that type will become a member of the extension.)
  */
 void
-GenerateTypeDependencies(Oid typeObjectId,
-						 Form_pg_type typeForm,
+GenerateTypeDependencies(HeapTuple typeTuple,
+						 Relation typeCatalog,
 						 Node *defaultExpr,
 						 void *typacl,
 						 char relationKind, /* only for relation rowtypes */
@@ -544,9 +548,30 @@ GenerateTypeDependencies(Oid typeObjectId,
 						 bool isDependentType,
 						 bool rebuild)
 {
+	Form_pg_type typeForm = (Form_pg_type) GETSTRUCT(typeTuple);
+	Oid			typeObjectId = typeForm->oid;
+	Datum		datum;
+	bool		isNull;
 	ObjectAddress myself,
 				referenced;
 
+	/* Extract defaultExpr if caller didn't pass it */
+	if (defaultExpr == NULL)
+	{
+		datum = heap_getattr(typeTuple, Anum_pg_type_typdefaultbin,
+							 RelationGetDescr(typeCatalog), &isNull);
+		if (!isNull)
+			defaultExpr = stringToNode(TextDatumGetCString(datum));
+	}
+	/* Extract typacl if caller didn't pass it */
+	if (typacl == NULL)
+	{
+		datum = heap_getattr(typeTuple, Anum_pg_type_typacl,
+							 RelationGetDescr(typeCatalog), &isNull);
+		if (!isNull)
+			typacl = DatumGetAclPCopy(datum);
+	}
+
 	/* If rebuild, first flush old dependencies, except extension deps */
 	if (rebuild)
 	{
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index d732a3a..e282d19 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -83,6 +83,25 @@ typedef struct
 	/* atts[] is of allocated length RelationGetNumberOfAttributes(rel) */
 } RelToCheck;
 
+/* parameter structure for AlterTypeRecurse() */
+typedef struct
+{
+	/* Flags indicating which type attributes to update */
+	bool		updateStorage;
+	bool		updateReceive;
+	bool		updateSend;
+	bool		updateTypmodin;
+	bool		updateTypmodout;
+	bool		updateAnalyze;
+	/* New values for relevant attributes */
+	char		storage;
+	Oid			receiveOid;
+	Oid			sendOid;
+	Oid			typmodinOid;
+	Oid			typmodoutOid;
+	Oid			analyzeOid;
+} AlterTypeRecurseParams;
+
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
 
@@ -107,6 +126,8 @@ static char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
 								 const char *domainName, ObjectAddress *constrAddr);
 static Node *replace_domain_constraint_value(ParseState *pstate,
 											 ColumnRef *cref);
+static void AlterTypeRecurse(Oid typeOid, HeapTuple tup, Relation catalog,
+							 AlterTypeRecurseParams *atparams);
 
 
 /*
@@ -466,9 +487,12 @@ DefineType(ParseState *pstate, List *names, List *parameters)
 	 * minimum sane check would be for execute-with-grant-option.  But we
 	 * don't have a way to make the type go away if the grant option is
 	 * revoked, so ownership seems better.
+	 *
+	 * XXX For now, this is all unnecessary given the superuser check above.
+	 * If we ever relax that, these calls likely should be moved into
+	 * findTypeInputFunction et al, where they could be shared by AlterType.
 	 */
 #ifdef NOT_USED
-	/* XXX this is unnecessary given the superuser check above */
 	if (inputOid && !pg_proc_ownercheck(inputOid, GetUserId()))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION,
 					   NameListToString(inputName));
@@ -493,47 +517,6 @@ DefineType(ParseState *pstate, List *names, List *parameters)
 #endif
 
 	/*
-	 * Print warnings if any of the type's I/O functions are marked volatile.
-	 * There is a general assumption that I/O functions are stable or
-	 * immutable; this allows us for example to mark record_in/record_out
-	 * stable rather than volatile.  Ideally we would throw errors not just
-	 * warnings here; but since this check is new as of 9.5, and since the
-	 * volatility marking might be just an error-of-omission and not a true
-	 * indication of how the function behaves, we'll let it pass as a warning
-	 * for now.
-	 */
-	if (inputOid && func_volatile(inputOid) == PROVOLATILE_VOLATILE)
-		ereport(WARNING,
-				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-				 errmsg("type input function %s should not be volatile",
-						NameListToString(inputName))));
-	if (outputOid && func_volatile(outputOid) == PROVOLATILE_VOLATILE)
-		ereport(WARNING,
-				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-				 errmsg("type output function %s should not be volatile",
-						NameListToString(outputName))));
-	if (receiveOid && func_volatile(receiveOid) == PROVOLATILE_VOLATILE)
-		ereport(WARNING,
-				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-				 errmsg("type receive function %s should not be volatile",
-						NameListToString(receiveName))));
-	if (sendOid && func_volatile(sendOid) == PROVOLATILE_VOLATILE)
-		ereport(WARNING,
-				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-				 errmsg("type send function %s should not be volatile",
-						NameListToString(sendName))));
-	if (typmodinOid && func_volatile(typmodinOid) == PROVOLATILE_VOLATILE)
-		ereport(WARNING,
-				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-				 errmsg("type modifier input function %s should not be volatile",
-						NameListToString(typmodinName))));
-	if (typmodoutOid && func_volatile(typmodoutOid) == PROVOLATILE_VOLATILE)
-		ereport(WARNING,
-				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-				 errmsg("type modifier output function %s should not be volatile",
-						NameListToString(typmodoutName))));
-
-	/*
 	 * OK, we're done checking, time to make the type.  We must assign the
 	 * array type OID ahead of calling TypeCreate, since the base type and
 	 * array type each refer to the other.
@@ -765,6 +748,12 @@ DefineDomain(CreateDomainStmt *stmt)
 		aclcheck_error_type(aclresult, basetypeoid);
 
 	/*
+	 * Collect the properties of the new domain.  Some are inherited from the
+	 * base type, some are not.  If you change any of this inheritance
+	 * behavior, be sure to update AlterTypeRecurse() to match!
+	 */
+
+	/*
 	 * Identify the collation if any
 	 */
 	baseColl = baseType->typcollation;
@@ -1664,6 +1653,22 @@ findTypeInputFunction(List *procname, Oid typeOid)
 				 errmsg("type input function %s must return type %s",
 						NameListToString(procname), format_type_be(typeOid))));
 
+	/*
+	 * Print warnings if any of the type's I/O functions are marked volatile.
+	 * There is a general assumption that I/O functions are stable or
+	 * immutable; this allows us for example to mark record_in/record_out
+	 * stable rather than volatile.  Ideally we would throw errors not just
+	 * warnings here; but since this check is new as of 9.5, and since the
+	 * volatility marking might be just an error-of-omission and not a true
+	 * indication of how the function behaves, we'll let it pass as a warning
+	 * for now.
+	 */
+	if (func_volatile(procOid) == PROVOLATILE_VOLATILE)
+		ereport(WARNING,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("type input function %s should not be volatile",
+						NameListToString(procname))));
+
 	return procOid;
 }
 
@@ -1692,6 +1697,13 @@ findTypeOutputFunction(List *procname, Oid typeOid)
 				 errmsg("type output function %s must return type %s",
 						NameListToString(procname), "cstring")));
 
+	/* Just a warning for now, per comments in findTypeInputFunction */
+	if (func_volatile(procOid) == PROVOLATILE_VOLATILE)
+		ereport(WARNING,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("type output function %s should not be volatile",
+						NameListToString(procname))));
+
 	return procOid;
 }
 
@@ -1728,6 +1740,13 @@ findTypeReceiveFunction(List *procname, Oid typeOid)
 				 errmsg("type receive function %s must return type %s",
 						NameListToString(procname), format_type_be(typeOid))));
 
+	/* Just a warning for now, per comments in findTypeInputFunction */
+	if (func_volatile(procOid) == PROVOLATILE_VOLATILE)
+		ereport(WARNING,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("type receive function %s should not be volatile",
+						NameListToString(procname))));
+
 	return procOid;
 }
 
@@ -1756,6 +1775,13 @@ findTypeSendFunction(List *procname, Oid typeOid)
 				 errmsg("type send function %s must return type %s",
 						NameListToString(procname), "bytea")));
 
+	/* Just a warning for now, per comments in findTypeInputFunction */
+	if (func_volatile(procOid) == PROVOLATILE_VOLATILE)
+		ereport(WARNING,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("type send function %s should not be volatile",
+						NameListToString(procname))));
+
 	return procOid;
 }
 
@@ -1783,6 +1809,13 @@ findTypeTypmodinFunction(List *procname)
 				 errmsg("typmod_in function %s must return type %s",
 						NameListToString(procname), "integer")));
 
+	/* Just a warning for now, per comments in findTypeInputFunction */
+	if (func_volatile(procOid) == PROVOLATILE_VOLATILE)
+		ereport(WARNING,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("type modifier input function %s should not be volatile",
+						NameListToString(procname))));
+
 	return procOid;
 }
 
@@ -1810,6 +1843,13 @@ findTypeTypmodoutFunction(List *procname)
 				 errmsg("typmod_out function %s must return type %s",
 						NameListToString(procname), "cstring")));
 
+	/* Just a warning for now, per comments in findTypeInputFunction */
+	if (func_volatile(procOid) == PROVOLATILE_VOLATILE)
+		ereport(WARNING,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("type modifier output function %s should not be volatile",
+						NameListToString(procname))));
+
 	return procOid;
 }
 
@@ -2086,9 +2126,6 @@ AlterDomainDefault(List *names, Node *defaultRaw)
 	Relation	rel;
 	char	   *defaultValue;
 	Node	   *defaultExpr = NULL; /* NULL if no default specified */
-	Acl		   *typacl;
-	Datum		aclDatum;
-	bool		isNull;
 	Datum		new_record[Natts_pg_type];
 	bool		new_record_nulls[Natts_pg_type];
 	bool		new_record_repl[Natts_pg_type];
@@ -2141,6 +2178,7 @@ AlterDomainDefault(List *names, Node *defaultRaw)
 			(IsA(defaultExpr, Const) &&((Const *) defaultExpr)->constisnull))
 		{
 			/* Default is NULL, drop it */
+			defaultExpr = NULL;
 			new_record_nulls[Anum_pg_type_typdefaultbin - 1] = true;
 			new_record_repl[Anum_pg_type_typdefaultbin - 1] = true;
 			new_record_nulls[Anum_pg_type_typdefault - 1] = true;
@@ -2181,19 +2219,11 @@ AlterDomainDefault(List *names, Node *defaultRaw)
 
 	CatalogTupleUpdate(rel, &tup->t_self, newtuple);
 
-	/* Must extract ACL for use of GenerateTypeDependencies */
-	aclDatum = heap_getattr(newtuple, Anum_pg_type_typacl,
-							RelationGetDescr(rel), &isNull);
-	if (isNull)
-		typacl = NULL;
-	else
-		typacl = DatumGetAclPCopy(aclDatum);
-
 	/* Rebuild dependencies */
-	GenerateTypeDependencies(domainoid,
-							 (Form_pg_type) GETSTRUCT(newtuple),
+	GenerateTypeDependencies(newtuple,
+							 rel,
 							 defaultExpr,
-							 typacl,
+							 NULL,	/* don't have typacl handy */
 							 0, /* relation kind is n/a */
 							 false, /* a domain isn't an implicit array */
 							 false, /* nor is it any kind of dependent type */
@@ -3609,3 +3639,358 @@ AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid,
 
 	return oldNspOid;
 }
+
+/*
+ * AlterType
+ *		ALTER TYPE <type> SET (option = ...)
+ *
+ * NOTE: the set of changes that can be allowed here is constrained by many
+ * non-obvious implementation restrictions.  Tread carefully when considering
+ * adding new flexibility.  One place to look at is whether typcache.c caches
+ * a particular type property.
+ */
+ObjectAddress
+AlterType(AlterTypeStmt *stmt)
+{
+	ObjectAddress address;
+	Relation	catalog;
+	TypeName   *typename;
+	HeapTuple	tup;
+	Oid			typeOid;
+	Form_pg_type typForm;
+	bool		requireSuper = false;
+	AlterTypeRecurseParams atparams;
+	ListCell   *pl;
+
+	catalog = table_open(TypeRelationId, RowExclusiveLock);
+
+	/* Make a TypeName so we can use standard type lookup machinery */
+	typename = makeTypeNameFromNameList(stmt->typeName);
+	tup = typenameType(NULL, typename, NULL);
+
+	typeOid = typeTypeId(tup);
+	typForm = (Form_pg_type) GETSTRUCT(tup);
+
+	/* Process options */
+	memset(&atparams, 0, sizeof(atparams));
+	foreach(pl, stmt->options)
+	{
+		DefElem    *defel = (DefElem *) lfirst(pl);
+
+		if (strcmp(defel->defname, "storage") == 0)
+		{
+			char	   *a = defGetString(defel);
+
+			if (pg_strcasecmp(a, "plain") == 0)
+				atparams.storage = TYPSTORAGE_PLAIN;
+			else if (pg_strcasecmp(a, "external") == 0)
+				atparams.storage = TYPSTORAGE_EXTERNAL;
+			else if (pg_strcasecmp(a, "extended") == 0)
+				atparams.storage = TYPSTORAGE_EXTENDED;
+			else if (pg_strcasecmp(a, "main") == 0)
+				atparams.storage = TYPSTORAGE_MAIN;
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("storage \"%s\" not recognized", a)));
+
+			/*
+			 * Validate the storage request.  If the type isn't varlena, it
+			 * certainly doesn't support non-PLAIN storage.
+			 */
+			if (atparams.storage != TYPSTORAGE_PLAIN && typForm->typlen != -1)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("fixed-size types must have storage PLAIN")));
+
+			/*
+			 * We disallow switching between PLAIN and non-PLAIN typstorage,
+			 * because that implies changing whether the type can be toasted
+			 * at all, which is problematic for a number of reasons. Switching
+			 * from PLAIN to non-PLAIN could perhaps be allowed to superusers,
+			 * who would then be responsible for ensuring that all the type's
+			 * C functions are TOAST-ready; but we'd need fixes in the
+			 * typcache and perhaps other places.  Switching from non-PLAIN to
+			 * PLAIN seems impractical because of the risk that toasted values
+			 * exist (and possibly more are getting created by concurrent
+			 * transactions).  But switching among different non-PLAIN
+			 * settings is OK, since it just constitutes a change in the
+			 * strategy requested for columns created in the future.
+			 */
+			if (atparams.storage != TYPSTORAGE_PLAIN &&
+				typForm->typstorage == TYPSTORAGE_PLAIN)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("cannot change type's storage from PLAIN")));
+			else if (atparams.storage == TYPSTORAGE_PLAIN &&
+					 typForm->typstorage != TYPSTORAGE_PLAIN)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("cannot change type's storage to PLAIN")));
+
+			atparams.updateStorage = true;
+		}
+		else if (strcmp(defel->defname, "receive") == 0)
+		{
+			if (defel->arg != NULL)
+				atparams.receiveOid =
+					findTypeReceiveFunction(defGetQualifiedName(defel),
+											typeOid);
+			else
+				atparams.receiveOid = InvalidOid;	/* NONE, remove function */
+			atparams.updateReceive = true;
+			/* Replacing an I/O function requires superuser. */
+			requireSuper = true;
+		}
+		else if (strcmp(defel->defname, "send") == 0)
+		{
+			if (defel->arg != NULL)
+				atparams.sendOid =
+					findTypeSendFunction(defGetQualifiedName(defel),
+										 typeOid);
+			else
+				atparams.sendOid = InvalidOid;	/* NONE, remove function */
+			atparams.updateSend = true;
+			/* Replacing an I/O function requires superuser. */
+			requireSuper = true;
+		}
+		else if (strcmp(defel->defname, "typmod_in") == 0)
+		{
+			if (defel->arg != NULL)
+				atparams.typmodinOid =
+					findTypeTypmodinFunction(defGetQualifiedName(defel));
+			else
+				atparams.typmodinOid = InvalidOid;	/* NONE, remove function */
+			atparams.updateTypmodin = true;
+			/* Replacing an I/O function requires superuser. */
+			requireSuper = true;
+		}
+		else if (strcmp(defel->defname, "typmod_out") == 0)
+		{
+			if (defel->arg != NULL)
+				atparams.typmodoutOid =
+					findTypeTypmodoutFunction(defGetQualifiedName(defel));
+			else
+				atparams.typmodoutOid = InvalidOid; /* NONE, remove function */
+			atparams.updateTypmodout = true;
+			/* Replacing an I/O function requires superuser. */
+			requireSuper = true;
+		}
+		else if (strcmp(defel->defname, "analyze") == 0)
+		{
+			if (defel->arg != NULL)
+				atparams.analyzeOid =
+					findTypeAnalyzeFunction(defGetQualifiedName(defel),
+											typeOid);
+			else
+				atparams.analyzeOid = InvalidOid;	/* NONE, remove function */
+			atparams.updateAnalyze = true;
+			/* Replacing an analyze function requires superuser. */
+			requireSuper = true;
+		}
+
+		/*
+		 * The rest of the options that CREATE accepts cannot be changed.
+		 * Check for them so that we can give a meaningful error message.
+		 */
+		else if (strcmp(defel->defname, "input") == 0 ||
+				 strcmp(defel->defname, "output") == 0 ||
+				 strcmp(defel->defname, "internallength") == 0 ||
+				 strcmp(defel->defname, "passedbyvalue") == 0 ||
+				 strcmp(defel->defname, "alignment") == 0 ||
+				 strcmp(defel->defname, "like") == 0 ||
+				 strcmp(defel->defname, "category") == 0 ||
+				 strcmp(defel->defname, "preferred") == 0 ||
+				 strcmp(defel->defname, "default") == 0 ||
+				 strcmp(defel->defname, "element") == 0 ||
+				 strcmp(defel->defname, "delimiter") == 0 ||
+				 strcmp(defel->defname, "collatable") == 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("type attribute \"%s\" cannot be changed",
+							defel->defname)));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("type attribute \"%s\" not recognized",
+							defel->defname)));
+	}
+
+	/*
+	 * Permissions check.  Require superuser if we decided the command
+	 * requires that, else must own the type.
+	 */
+	if (requireSuper)
+	{
+		if (!superuser())
+			ereport(ERROR,
+					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+					 errmsg("must be superuser to alter a type")));
+	}
+	else
+	{
+		if (!pg_type_ownercheck(typeOid, GetUserId()))
+			aclcheck_error_type(ACLCHECK_NOT_OWNER, typeOid);
+	}
+
+	/*
+	 * We disallow all forms of ALTER TYPE SET on types that aren't plain base
+	 * types.  It would for example be highly unsafe, not to mention
+	 * pointless, to change the send/receive functions for a composite type.
+	 * Moreover, pg_dump has no support for changing these properties on
+	 * non-base types.  We might weaken this someday, but not now.
+	 *
+	 * Note: if you weaken this enough to allow composite types, be sure to
+	 * adjust the GenerateTypeDependencies call in AlterTypeRecurse.
+	 */
+	if (typForm->typtype != TYPTYPE_BASE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("%s is not a base type",
+						format_type_be(typeOid))));
+
+	/*
+	 * For the same reasons, don't allow direct alteration of array types.
+	 */
+	if (OidIsValid(typForm->typelem) &&
+		get_array_type(typForm->typelem) == typeOid)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("%s is not a base type",
+						format_type_be(typeOid))));
+
+	/* OK, recursively update this type and any domains over it */
+	AlterTypeRecurse(typeOid, tup, catalog, &atparams);
+
+	/* Clean up */
+	ReleaseSysCache(tup);
+
+	table_close(catalog, RowExclusiveLock);
+
+	ObjectAddressSet(address, TypeRelationId, typeOid);
+
+	return address;
+}
+
+/*
+ * AlterTypeRecurse: one recursion step for AlterType()
+ *
+ * Apply the changes specified by "atparams" to the type identified by
+ * "typeOid", whose existing pg_type tuple is "tup".  Then search for any
+ * domains over this type, and recursively apply (most of) the same changes
+ * to those domains.
+ *
+ * We need this because the system generally assumes that a domain inherits
+ * many properties from its base type.  See DefineDomain() above for details
+ * of what is inherited.
+ *
+ * There's a race condition here, in that some other transaction could
+ * concurrently add another domain atop this base type; we'd miss updating
+ * that one.  Hence, be wary of allowing ALTER TYPE to change properties for
+ * which it'd be really fatal for a domain to be out of sync with its base
+ * type (typlen, for example).  In practice, races seem unlikely to be an
+ * issue for plausible use-cases for ALTER TYPE.  If one does happen, it could
+ * be fixed by re-doing the same ALTER TYPE once all prior transactions have
+ * committed.
+ */
+static void
+AlterTypeRecurse(Oid typeOid, HeapTuple tup, Relation catalog,
+				 AlterTypeRecurseParams *atparams)
+{
+	Datum		values[Natts_pg_type];
+	bool		nulls[Natts_pg_type];
+	bool		replaces[Natts_pg_type];
+	HeapTuple	newtup;
+	SysScanDesc scan;
+	ScanKeyData key[1];
+	HeapTuple	domainTup;
+
+	/* Since this function recurses, it could be driven to stack overflow */
+	check_stack_depth();
+
+	/* Update the current type's tuple */
+	memset(values, 0, sizeof(values));
+	memset(nulls, 0, sizeof(nulls));
+	memset(replaces, 0, sizeof(replaces));
+
+	if (atparams->updateStorage)
+	{
+		replaces[Anum_pg_type_typstorage - 1] = true;
+		values[Anum_pg_type_typstorage - 1] = CharGetDatum(atparams->storage);
+	}
+	if (atparams->updateReceive)
+	{
+		replaces[Anum_pg_type_typreceive - 1] = true;
+		values[Anum_pg_type_typreceive - 1] = ObjectIdGetDatum(atparams->receiveOid);
+	}
+	if (atparams->updateSend)
+	{
+		replaces[Anum_pg_type_typsend - 1] = true;
+		values[Anum_pg_type_typsend - 1] = ObjectIdGetDatum(atparams->sendOid);
+	}
+	if (atparams->updateTypmodin)
+	{
+		replaces[Anum_pg_type_typmodin - 1] = true;
+		values[Anum_pg_type_typmodin - 1] = ObjectIdGetDatum(atparams->typmodinOid);
+	}
+	if (atparams->updateTypmodout)
+	{
+		replaces[Anum_pg_type_typmodout - 1] = true;
+		values[Anum_pg_type_typmodout - 1] = ObjectIdGetDatum(atparams->typmodoutOid);
+	}
+	if (atparams->updateAnalyze)
+	{
+		replaces[Anum_pg_type_typanalyze - 1] = true;
+		values[Anum_pg_type_typanalyze - 1] = ObjectIdGetDatum(atparams->analyzeOid);
+	}
+
+	newtup = heap_modify_tuple(tup, RelationGetDescr(catalog),
+							   values, nulls, replaces);
+
+	CatalogTupleUpdate(catalog, &newtup->t_self, newtup);
+
+	/* Rebuild dependencies for this type */
+	GenerateTypeDependencies(newtup,
+							 catalog,
+							 NULL,	/* don't have defaultExpr handy */
+							 NULL,	/* don't have typacl handy */
+							 0, /* we rejected composite types above */
+							 false, /* and we rejected implicit arrays above */
+							 false, /* so it can't be a dependent type */
+							 true);
+
+	InvokeObjectPostAlterHook(TypeRelationId, typeOid, 0);
+
+	/*
+	 * Now we need to recurse to domains.  However, some properties are not
+	 * inherited by domains, so clear the update flags for those.
+	 */
+	atparams->updateReceive = false;	/* domains use F_DOMAIN_RECV */
+	atparams->updateTypmodin = false;	/* domains don't have typmods */
+	atparams->updateTypmodout = false;
+
+	/* Search pg_type for possible domains over this type */
+	ScanKeyInit(&key[0],
+				Anum_pg_type_typbasetype,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(typeOid));
+
+	scan = systable_beginscan(catalog, InvalidOid, false,
+							  NULL, 1, key);
+
+	while ((domainTup = systable_getnext(scan)) != NULL)
+	{
+		Form_pg_type domainForm = (Form_pg_type) GETSTRUCT(domainTup);
+
+		/*
+		 * Shouldn't have a nonzero typbasetype in a non-domain, but let's
+		 * check
+		 */
+		if (domainForm->typtype != TYPTYPE_DOMAIN)
+			continue;
+
+		AlterTypeRecurse(domainForm->oid, domainTup, catalog, atparams);
+	}
+
+	systable_endscan(scan);
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index e04c33e..eaab97f 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3636,6 +3636,17 @@ _copyAlterOperatorStmt(const AlterOperatorStmt *from)
 	return newnode;
 }
 
+static AlterTypeStmt *
+_copyAlterTypeStmt(const AlterTypeStmt *from)
+{
+	AlterTypeStmt *newnode = makeNode(AlterTypeStmt);
+
+	COPY_NODE_FIELD(typeName);
+	COPY_NODE_FIELD(options);
+
+	return newnode;
+}
+
 static RuleStmt *
 _copyRuleStmt(const RuleStmt *from)
 {
@@ -5263,6 +5274,9 @@ copyObjectImpl(const void *from)
 		case T_AlterOperatorStmt:
 			retval = _copyAlterOperatorStmt(from);
 			break;
+		case T_AlterTypeStmt:
+			retval = _copyAlterTypeStmt(from);
+			break;
 		case T_RuleStmt:
 			retval = _copyRuleStmt(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 5b1ba14..88b9129 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1482,6 +1482,15 @@ _equalAlterOperatorStmt(const AlterOperatorStmt *a, const AlterOperatorStmt *b)
 }
 
 static bool
+_equalAlterTypeStmt(const AlterTypeStmt *a, const AlterTypeStmt *b)
+{
+	COMPARE_NODE_FIELD(typeName);
+	COMPARE_NODE_FIELD(options);
+
+	return true;
+}
+
+static bool
 _equalRuleStmt(const RuleStmt *a, const RuleStmt *b)
 {
 	COMPARE_NODE_FIELD(relation);
@@ -3359,6 +3368,9 @@ equal(const void *a, const void *b)
 		case T_AlterOperatorStmt:
 			retval = _equalAlterOperatorStmt(a, b);
 			break;
+		case T_AlterTypeStmt:
+			retval = _equalAlterTypeStmt(a, b);
+			break;
 		case T_RuleStmt:
 			retval = _equalRuleStmt(a, b);
 			break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 96e7fdb..7e384f9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -249,7 +249,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
 		AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
 		AlterObjectDependsStmt AlterObjectSchemaStmt AlterOwnerStmt
-		AlterOperatorStmt AlterSeqStmt AlterSystemStmt AlterTableStmt
+		AlterOperatorStmt AlterTypeStmt AlterSeqStmt AlterSystemStmt AlterTableStmt
 		AlterTblSpcStmt AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt
 		AlterCompositeTypeStmt AlterUserMappingStmt
 		AlterRoleStmt AlterRoleSetStmt AlterPolicyStmt AlterStatsStmt
@@ -847,6 +847,7 @@ stmt :
 			| AlterObjectSchemaStmt
 			| AlterOwnerStmt
 			| AlterOperatorStmt
+			| AlterTypeStmt
 			| AlterPolicyStmt
 			| AlterSeqStmt
 			| AlterSystemStmt
@@ -9367,6 +9368,24 @@ operator_def_arg:
 
 /*****************************************************************************
  *
+ * ALTER TYPE name SET define
+ *
+ * We repurpose ALTER OPERATOR's version of "definition" here
+ *
+ *****************************************************************************/
+
+AlterTypeStmt:
+			ALTER TYPE_P any_name SET '(' operator_def_list ')'
+				{
+					AlterTypeStmt *n = makeNode(AlterTypeStmt);
+					n->typeName = $3;
+					n->options = $6;
+					$$ = (Node *)n;
+				}
+		;
+
+/*****************************************************************************
+ *
  * ALTER THING name OWNER TO newname
  *
  *****************************************************************************/
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 1b460a2..b1f7f6e 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -162,6 +162,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_AlterTableMoveAllStmt:
 		case T_AlterTableSpaceOptionsStmt:
 		case T_AlterTableStmt:
+		case T_AlterTypeStmt:
 		case T_AlterUserMappingStmt:
 		case T_CommentStmt:
 		case T_CompositeTypeStmt:
@@ -1713,6 +1714,10 @@ ProcessUtilitySlow(ParseState *pstate,
 				address = AlterOperator((AlterOperatorStmt *) parsetree);
 				break;
 
+			case T_AlterTypeStmt:
+				address = AlterType((AlterTypeStmt *) parsetree);
+				break;
+
 			case T_CommentStmt:
 				address = CommentObject((CommentStmt *) parsetree);
 				break;
@@ -2895,6 +2900,10 @@ CreateCommandTag(Node *parsetree)
 			tag = CMDTAG_ALTER_OPERATOR;
 			break;
 
+		case T_AlterTypeStmt:
+			tag = CMDTAG_ALTER_TYPE;
+			break;
+
 		case T_AlterTSDictionaryStmt:
 			tag = CMDTAG_ALTER_TEXT_SEARCH_DICTIONARY;
 			break;
@@ -3251,6 +3260,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_AlterTypeStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
 		case T_AlterTableMoveAllStmt:
 		case T_AlterTableStmt:
 			lev = LOGSTMT_DDL;
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 490bc2a..e34daaa 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -66,9 +66,9 @@ static char *range_deparse(char flags, const char *lbound_str,
 						   const char *ubound_str);
 static char *range_bound_escape(const char *value);
 static Size datum_compute_size(Size sz, Datum datum, bool typbyval,
-							   char typalign, int16 typlen, char typstorage);
+							   char typalign, int16 typlen, bool type_is_packable);
 static Pointer datum_write(Pointer ptr, Datum datum, bool typbyval,
-						   char typalign, int16 typlen, char typstorage);
+						   char typalign, int16 typlen, bool type_is_packable);
 
 
 /*
@@ -1577,7 +1577,7 @@ range_serialize(TypeCacheEntry *typcache, RangeBound *lower, RangeBound *upper,
 	int16		typlen;
 	bool		typbyval;
 	char		typalign;
-	char		typstorage;
+	bool		type_is_packable;
 	char		flags = 0;
 
 	/*
@@ -1620,7 +1620,7 @@ range_serialize(TypeCacheEntry *typcache, RangeBound *lower, RangeBound *upper,
 	typlen = typcache->rngelemtype->typlen;
 	typbyval = typcache->rngelemtype->typbyval;
 	typalign = typcache->rngelemtype->typalign;
-	typstorage = typcache->rngelemtype->typstorage;
+	type_is_packable = typcache->rngelemtype->type_is_packable;
 
 	/* Count space for varlena header and range type's OID */
 	msize = sizeof(RangeType);
@@ -1642,7 +1642,7 @@ range_serialize(TypeCacheEntry *typcache, RangeBound *lower, RangeBound *upper,
 			lower->val = PointerGetDatum(PG_DETOAST_DATUM_PACKED(lower->val));
 
 		msize = datum_compute_size(msize, lower->val, typbyval, typalign,
-								   typlen, typstorage);
+								   typlen, type_is_packable);
 	}
 
 	if (RANGE_HAS_UBOUND(flags))
@@ -1652,7 +1652,7 @@ range_serialize(TypeCacheEntry *typcache, RangeBound *lower, RangeBound *upper,
 			upper->val = PointerGetDatum(PG_DETOAST_DATUM_PACKED(upper->val));
 
 		msize = datum_compute_size(msize, upper->val, typbyval, typalign,
-								   typlen, typstorage);
+								   typlen, type_is_packable);
 	}
 
 	/* Add space for flag byte */
@@ -1671,14 +1671,14 @@ range_serialize(TypeCacheEntry *typcache, RangeBound *lower, RangeBound *upper,
 	{
 		Assert(lower->lower);
 		ptr = datum_write(ptr, lower->val, typbyval, typalign, typlen,
-						  typstorage);
+						  type_is_packable);
 	}
 
 	if (RANGE_HAS_UBOUND(flags))
 	{
 		Assert(!upper->lower);
 		ptr = datum_write(ptr, upper->val, typbyval, typalign, typlen,
-						  typstorage);
+						  type_is_packable);
 	}
 
 	*((char *) ptr) = flags;
@@ -2383,24 +2383,18 @@ range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum
  * datum_compute_size() and datum_write() are used to insert the bound
  * values into a range object.  They are modeled after heaptuple.c's
  * heap_compute_data_size() and heap_fill_tuple(), but we need not handle
- * null values here.  TYPE_IS_PACKABLE must test the same conditions as
- * heaptuple.c's ATT_IS_PACKABLE macro.
+ * null values here.
  */
 
-/* Does datatype allow packing into the 1-byte-header varlena format? */
-#define TYPE_IS_PACKABLE(typlen, typstorage) \
-	((typlen) == -1 && (typstorage) != TYPSTORAGE_PLAIN)
-
 /*
  * Increment data_length by the space needed by the datum, including any
  * preceding alignment padding.
  */
 static Size
 datum_compute_size(Size data_length, Datum val, bool typbyval, char typalign,
-				   int16 typlen, char typstorage)
+				   int16 typlen, bool type_is_packable)
 {
-	if (TYPE_IS_PACKABLE(typlen, typstorage) &&
-		VARATT_CAN_MAKE_SHORT(DatumGetPointer(val)))
+	if (type_is_packable && VARATT_CAN_MAKE_SHORT(DatumGetPointer(val)))
 	{
 		/*
 		 * we're anticipating converting to a short varlena header, so adjust
@@ -2423,7 +2417,7 @@ datum_compute_size(Size data_length, Datum val, bool typbyval, char typalign,
  */
 static Pointer
 datum_write(Pointer ptr, Datum datum, bool typbyval, char typalign,
-			int16 typlen, char typstorage)
+			int16 typlen, bool type_is_packable)
 {
 	Size		data_length;
 
@@ -2454,8 +2448,7 @@ datum_write(Pointer ptr, Datum datum, bool typbyval, char typalign,
 			data_length = VARSIZE_SHORT(val);
 			memcpy(ptr, val, data_length);
 		}
-		else if (TYPE_IS_PACKABLE(typlen, typstorage) &&
-				 VARATT_CAN_MAKE_SHORT(val))
+		else if (type_is_packable && VARATT_CAN_MAKE_SHORT(val))
 		{
 			/* convert to short varlena -- no alignment */
 			data_length = VARATT_CONVERTED_SHORT_SIZE(val);
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index cdf6331..88913ca 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -382,11 +382,21 @@ lookup_type_cache(Oid type_id, int flags)
 
 		MemSet(typentry, 0, sizeof(TypeCacheEntry));
 		typentry->type_id = type_id;
+
+		/*
+		 * Caution: there's no provision for flushing/rebuilding these basic
+		 * cache fields; they'll remain for the life of the backend.  Hence,
+		 * do not cache anything here that ALTER TYPE can change.  (Although
+		 * we allow ALTER TYPE to change typstorage, it can't change it
+		 * between PLAIN and not-PLAIN, so caching the packable flag is safe.)
+		 */
 		typentry->typlen = typtup->typlen;
 		typentry->typbyval = typtup->typbyval;
 		typentry->typalign = typtup->typalign;
-		typentry->typstorage = typtup->typstorage;
 		typentry->typtype = typtup->typtype;
+		/* This test must match heaptuple.c's ATT_IS_PACKABLE macro */
+		typentry->type_is_packable = (typtup->typlen == -1 &&
+									  typtup->typstorage != TYPSTORAGE_PLAIN);
 		typentry->typrelid = typtup->typrelid;
 		typentry->typelem = typtup->typelem;
 		typentry->typcollation = typtup->typcollation;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b6b08d0..54d0317 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2150,7 +2150,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("ALTER", "TYPE", MatchAny))
 		COMPLETE_WITH("ADD ATTRIBUTE", "ADD VALUE", "ALTER ATTRIBUTE",
 					  "DROP ATTRIBUTE",
-					  "OWNER TO", "RENAME", "SET SCHEMA");
+					  "OWNER TO", "RENAME", "SET SCHEMA", "SET (");
 	/* complete ALTER TYPE <foo> ADD with actions */
 	else if (Matches("ALTER", "TYPE", MatchAny, "ADD"))
 		COMPLETE_WITH("ATTRIBUTE", "VALUE");
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index f972f94..9789094 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -340,8 +340,8 @@ extern ObjectAddress TypeCreate(Oid newTypeOid,
 								bool typeNotNull,
 								Oid typeCollation);
 
-extern void GenerateTypeDependencies(Oid typeObjectId,
-									 Form_pg_type typeForm,
+extern void GenerateTypeDependencies(HeapTuple typeTuple,
+									 Relation typeCatalog,
 									 Node *defaultExpr,
 									 void *typacl,
 									 char relationKind, /* only for relation
diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h
index fc18d64..0162bc2 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -54,4 +54,6 @@ extern Oid	AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid,
 									   bool errorOnTableType,
 									   ObjectAddresses *objsMoved);
 
+extern ObjectAddress AlterType(AlterTypeStmt *stmt);
+
 #endif							/* TYPECMDS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index baced7e..8a76afe 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -380,6 +380,7 @@ typedef enum NodeTag
 	T_AlterObjectSchemaStmt,
 	T_AlterOwnerStmt,
 	T_AlterOperatorStmt,
+	T_AlterTypeStmt,
 	T_DropOwnedStmt,
 	T_ReassignOwnedStmt,
 	T_CompositeTypeStmt,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index da0706a..2039b42 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2959,9 +2959,8 @@ typedef struct AlterOwnerStmt
 	RoleSpec   *newowner;		/* the new owner */
 } AlterOwnerStmt;
 
-
 /* ----------------------
- *		Alter Operator Set Restrict, Join
+ *		Alter Operator Set ( this-n-that )
  * ----------------------
  */
 typedef struct AlterOperatorStmt
@@ -2971,6 +2970,16 @@ typedef struct AlterOperatorStmt
 	List	   *options;		/* List of DefElem nodes */
 } AlterOperatorStmt;
 
+/* ------------------------
+ *		Alter Type Set ( this-n-that )
+ * ------------------------
+ */
+typedef struct AlterTypeStmt
+{
+	NodeTag		type;
+	List	   *typeName;		/* type name (possibly qualified) */
+	List	   *options;		/* List of DefElem nodes */
+} AlterTypeStmt;
 
 /* ----------------------
  *		Create Rule Statement
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index 66ff17d..77b9a90 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -33,12 +33,16 @@ typedef struct TypeCacheEntry
 	/* typeId is the hash lookup key and MUST BE FIRST */
 	Oid			type_id;		/* OID of the data type */
 
-	/* some subsidiary information copied from the pg_type row */
+	/*
+	 * Some subsidiary information copied from the pg_type row.  Caution: do
+	 * not cache anything here that ALTER TYPE can change, since there's no
+	 * provision for flushing this part of the cache.
+	 */
 	int16		typlen;
 	bool		typbyval;
 	char		typalign;
-	char		typstorage;
 	char		typtype;
+	bool		type_is_packable;
 	Oid			typrelid;
 	Oid			typelem;
 	Oid			typcollation;
diff --git a/src/test/regress/expected/create_type.out b/src/test/regress/expected/create_type.out
index eb55e25..86a8b65 100644
--- a/src/test/regress/expected/create_type.out
+++ b/src/test/regress/expected/create_type.out
@@ -224,3 +224,81 @@ select format_type('bpchar'::regtype, -1);
  bpchar
 (1 row)
 
+--
+-- Test CREATE/ALTER TYPE using a type that's compatible with varchar,
+-- so we can re-use those support functions
+--
+CREATE TYPE myvarchar;
+CREATE FUNCTION myvarcharin(cstring, oid, integer) RETURNS myvarchar
+LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT AS 'varcharin';
+NOTICE:  return type myvarchar is only a shell
+CREATE FUNCTION myvarcharout(myvarchar) RETURNS cstring
+LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT AS 'varcharout';
+NOTICE:  argument type myvarchar is only a shell
+CREATE FUNCTION myvarcharsend(myvarchar) RETURNS bytea
+LANGUAGE internal STABLE PARALLEL SAFE STRICT AS 'varcharsend';
+NOTICE:  argument type myvarchar is only a shell
+CREATE FUNCTION myvarcharrecv(internal, oid, integer) RETURNS myvarchar
+LANGUAGE internal STABLE PARALLEL SAFE STRICT AS 'varcharrecv';
+NOTICE:  return type myvarchar is only a shell
+-- fail, it's still a shell:
+ALTER TYPE myvarchar SET (storage = extended);
+ERROR:  type "myvarchar" is only a shell
+CREATE TYPE myvarchar (
+    input = myvarcharin,
+    output = myvarcharout,
+    alignment = integer,
+    storage = main
+);
+-- want to check updating of a domain over the target type, too
+CREATE DOMAIN myvarchardom AS myvarchar;
+ALTER TYPE myvarchar SET (storage = plain);  -- not allowed
+ERROR:  cannot change type's storage to PLAIN
+ALTER TYPE myvarchar SET (storage = extended);
+ALTER TYPE myvarchar SET (
+    send = myvarcharsend,
+    receive = myvarcharrecv,
+    typmod_in = varchartypmodin,
+    typmod_out = varchartypmodout,
+    analyze = array_typanalyze  -- bogus, but it doesn't matter
+);
+SELECT typinput, typoutput, typreceive, typsend, typmodin, typmodout,
+       typanalyze, typstorage
+FROM pg_type WHERE typname = 'myvarchar';
+  typinput   |  typoutput   |  typreceive   |    typsend    |    typmodin     |    typmodout     |    typanalyze    | typstorage 
+-------------+--------------+---------------+---------------+-----------------+------------------+------------------+------------
+ myvarcharin | myvarcharout | myvarcharrecv | myvarcharsend | varchartypmodin | varchartypmodout | array_typanalyze | x
+(1 row)
+
+SELECT typinput, typoutput, typreceive, typsend, typmodin, typmodout,
+       typanalyze, typstorage
+FROM pg_type WHERE typname = 'myvarchardom';
+ typinput  |  typoutput   | typreceive  |    typsend    | typmodin | typmodout |    typanalyze    | typstorage 
+-----------+--------------+-------------+---------------+----------+-----------+------------------+------------
+ domain_in | myvarcharout | domain_recv | myvarcharsend | -        | -         | array_typanalyze | x
+(1 row)
+
+-- ensure dependencies are straight
+DROP FUNCTION myvarcharsend(myvarchar);  -- fail
+ERROR:  cannot drop function myvarcharsend(myvarchar) because other objects depend on it
+DETAIL:  type myvarchar depends on function myvarcharsend(myvarchar)
+function myvarcharin(cstring,oid,integer) depends on type myvarchar
+function myvarcharout(myvarchar) depends on type myvarchar
+function myvarcharrecv(internal,oid,integer) depends on type myvarchar
+type myvarchardom depends on function myvarcharsend(myvarchar)
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TYPE myvarchar;  -- fail
+ERROR:  cannot drop type myvarchar because other objects depend on it
+DETAIL:  function myvarcharin(cstring,oid,integer) depends on type myvarchar
+function myvarcharout(myvarchar) depends on type myvarchar
+function myvarcharsend(myvarchar) depends on type myvarchar
+function myvarcharrecv(internal,oid,integer) depends on type myvarchar
+type myvarchardom depends on type myvarchar
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TYPE myvarchar CASCADE;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to function myvarcharin(cstring,oid,integer)
+drop cascades to function myvarcharout(myvarchar)
+drop cascades to function myvarcharsend(myvarchar)
+drop cascades to function myvarcharrecv(internal,oid,integer)
+drop cascades to type myvarchardom
diff --git a/src/test/regress/sql/create_type.sql b/src/test/regress/sql/create_type.sql
index 68b04fd..5b176bb 100644
--- a/src/test/regress/sql/create_type.sql
+++ b/src/test/regress/sql/create_type.sql
@@ -166,3 +166,60 @@ select format_type('varchar'::regtype, 42);
 select format_type('bpchar'::regtype, null);
 -- this behavior difference is intentional
 select format_type('bpchar'::regtype, -1);
+
+--
+-- Test CREATE/ALTER TYPE using a type that's compatible with varchar,
+-- so we can re-use those support functions
+--
+CREATE TYPE myvarchar;
+
+CREATE FUNCTION myvarcharin(cstring, oid, integer) RETURNS myvarchar
+LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT AS 'varcharin';
+
+CREATE FUNCTION myvarcharout(myvarchar) RETURNS cstring
+LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT AS 'varcharout';
+
+CREATE FUNCTION myvarcharsend(myvarchar) RETURNS bytea
+LANGUAGE internal STABLE PARALLEL SAFE STRICT AS 'varcharsend';
+
+CREATE FUNCTION myvarcharrecv(internal, oid, integer) RETURNS myvarchar
+LANGUAGE internal STABLE PARALLEL SAFE STRICT AS 'varcharrecv';
+
+-- fail, it's still a shell:
+ALTER TYPE myvarchar SET (storage = extended);
+
+CREATE TYPE myvarchar (
+    input = myvarcharin,
+    output = myvarcharout,
+    alignment = integer,
+    storage = main
+);
+
+-- want to check updating of a domain over the target type, too
+CREATE DOMAIN myvarchardom AS myvarchar;
+
+ALTER TYPE myvarchar SET (storage = plain);  -- not allowed
+
+ALTER TYPE myvarchar SET (storage = extended);
+
+ALTER TYPE myvarchar SET (
+    send = myvarcharsend,
+    receive = myvarcharrecv,
+    typmod_in = varchartypmodin,
+    typmod_out = varchartypmodout,
+    analyze = array_typanalyze  -- bogus, but it doesn't matter
+);
+
+SELECT typinput, typoutput, typreceive, typsend, typmodin, typmodout,
+       typanalyze, typstorage
+FROM pg_type WHERE typname = 'myvarchar';
+
+SELECT typinput, typoutput, typreceive, typsend, typmodin, typmodout,
+       typanalyze, typstorage
+FROM pg_type WHERE typname = 'myvarchardom';
+
+-- ensure dependencies are straight
+DROP FUNCTION myvarcharsend(myvarchar);  -- fail
+DROP TYPE myvarchar;  -- fail
+
+DROP TYPE myvarchar CASCADE;
