Re: Allowing ALTER TYPE to change storage strategy - Mailing list pgsql-hackers

From Tom Lane
Subject Re: Allowing ALTER TYPE to change storage strategy
Date
Msg-id 27393.1583366202@sss.pgh.pa.us
Whole thread Raw
In response to Re: Allowing ALTER TYPE to change storage strategy  (Tom Lane <tgl@sss.pgh.pa.us>)
Responses Re: Allowing ALTER TYPE to change storage strategy
List pgsql-hackers
I wrote:
> 3. Drop the ability for ALTER TYPE to promote from PLAIN to not-PLAIN
> typstorage, and adjust the typcache so that it only remembers boolean
> toastability not the specific toasting strategy.  Then the cache is
> still immutable so no need for update logic.
>
> I'm kind of liking #3, ugly as it sounds, because I'm not sure how
> much of a use-case there is for the upgrade-from-PLAIN case.
> Particularly now that TOAST is so ingrained in the system, it seems
> rather unlikely that a production-grade data type wouldn't have
> been designed to be toastable from the beginning, if there could be
> any advantage to that.  Either #1 or #2 seem like mighty high prices
> to pay for offering an option that might have no real-world uses.

Here's a v5 based on that approach.  I also added some comments about
the potential race conditions involved in recursing to domains.

            regards, tom lane

diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml
index 175315f..111f8e6 100644
--- a/doc/src/sgml/ref/create_type.sgml
+++ b/doc/src/sgml/ref/create_type.sgml
@@ -823,18 +823,6 @@ CREATE TYPE <replaceable class="parameter">name</replaceable>
    function is written in C.
   </para>

-  <para>
-   In <productname>PostgreSQL</productname> versions before 7.3, it
-   was customary to avoid creating a shell type at all, by replacing the
-   functions' forward references to the type name with the placeholder
-   pseudo-type <type>opaque</type>.  The <type>cstring</type> arguments and
-   results also had to be declared as <type>opaque</type> before 7.3.  To
-   support loading of old dump files, <command>CREATE TYPE</command> will
-   accept I/O functions declared using <type>opaque</type>, but it will issue
-   a notice and change the function declarations to use the correct
-   types.
-  </para>
-
  </refsect1>

  <refsect1>
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 540044b..9279c05 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -1444,50 +1444,6 @@ SetFunctionReturnType(Oid funcOid, Oid newRetType)


 /*
- * SetFunctionArgType - change declared argument type of a function
- *
- * As above, but change an argument's type.
- */
-void
-SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType)
-{
-    Relation    pg_proc_rel;
-    HeapTuple    tup;
-    Form_pg_proc procForm;
-    ObjectAddress func_address;
-    ObjectAddress type_address;
-
-    pg_proc_rel = table_open(ProcedureRelationId, RowExclusiveLock);
-
-    tup = SearchSysCacheCopy1(PROCOID, ObjectIdGetDatum(funcOid));
-    if (!HeapTupleIsValid(tup)) /* should not happen */
-        elog(ERROR, "cache lookup failed for function %u", funcOid);
-    procForm = (Form_pg_proc) GETSTRUCT(tup);
-
-    if (argIndex < 0 || argIndex >= procForm->pronargs ||
-        procForm->proargtypes.values[argIndex] != OPAQUEOID)
-        elog(ERROR, "function %u doesn't take OPAQUE", funcOid);
-
-    /* okay to overwrite copied tuple */
-    procForm->proargtypes.values[argIndex] = newArgType;
-
-    /* update the catalog and its indexes */
-    CatalogTupleUpdate(pg_proc_rel, &tup->t_self, tup);
-
-    table_close(pg_proc_rel, RowExclusiveLock);
-
-    /*
-     * Also update the dependency to the new type. Opaque is a pinned type, so
-     * there is no old dependency record for it that we would need to remove.
-     */
-    ObjectAddressSet(type_address, TypeRelationId, newArgType);
-    ObjectAddressSet(func_address, ProcedureRelationId, funcOid);
-    recordDependencyOn(&func_address, &type_address, DEPENDENCY_NORMAL);
-}
-
-
-
-/*
  * CREATE CAST
  */
 ObjectAddress
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 5209736..d6e694e 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -163,7 +163,6 @@ DefineType(ParseState *pstate, List *names, List *parameters)
     char       *array_type;
     Oid            array_oid;
     Oid            typoid;
-    Oid            resulttype;
     ListCell   *pl;
     ObjectAddress address;

@@ -196,8 +195,7 @@ DefineType(ParseState *pstate, List *names, List *parameters)
 #endif

     /*
-     * Look to see if type already exists (presumably as a shell; if not,
-     * TypeCreate will complain).
+     * Look to see if type already exists.
      */
     typoid = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid,
                              CStringGetDatum(typeName),
@@ -211,35 +209,37 @@ DefineType(ParseState *pstate, List *names, List *parameters)
     {
         if (moveArrayTypeName(typoid, typeName, typeNamespace))
             typoid = InvalidOid;
+        else
+            ereport(ERROR,
+                    (errcode(ERRCODE_DUPLICATE_OBJECT),
+                     errmsg("type \"%s\" already exists", typeName)));
     }

     /*
-     * If it doesn't exist, create it as a shell, so that the OID is known for
-     * use in the I/O function definitions.
+     * If this command is a parameterless CREATE TYPE, then we're just here to
+     * make a shell type, so do that (or fail if there already is a shell).
      */
-    if (!OidIsValid(typoid))
+    if (parameters == NIL)
     {
-        address = TypeShellMake(typeName, typeNamespace, GetUserId());
-        typoid = address.objectId;
-        /* Make new shell type visible for modification below */
-        CommandCounterIncrement();
-
-        /*
-         * If the command was a parameterless CREATE TYPE, we're done ---
-         * creating the shell type was all we're supposed to do.
-         */
-        if (parameters == NIL)
-            return address;
-    }
-    else
-    {
-        /* Complain if dummy CREATE TYPE and entry already exists */
-        if (parameters == NIL)
+        if (OidIsValid(typoid))
             ereport(ERROR,
                     (errcode(ERRCODE_DUPLICATE_OBJECT),
                      errmsg("type \"%s\" already exists", typeName)));
+
+        address = TypeShellMake(typeName, typeNamespace, GetUserId());
+        return address;
     }

+    /*
+     * Otherwise, we must already have a shell type, since there is no other
+     * way that the I/O functions could have been created.
+     */
+    if (!OidIsValid(typoid))
+        ereport(ERROR,
+                (errcode(ERRCODE_DUPLICATE_OBJECT),
+                 errmsg("type \"%s\" does not exist", typeName),
+                 errhint("Create the type as a shell type, then create its I/O functions, then do a full CREATE
TYPE.")));
+
     /* Extract the parameters from the parameter list */
     foreach(pl, parameters)
     {
@@ -445,63 +445,6 @@ DefineType(ParseState *pstate, List *names, List *parameters)
         sendOid = findTypeSendFunction(sendName, typoid);

     /*
-     * Verify that I/O procs return the expected thing.  If we see OPAQUE,
-     * complain and change it to the correct type-safe choice.
-     */
-    resulttype = get_func_rettype(inputOid);
-    if (resulttype != typoid)
-    {
-        if (resulttype == OPAQUEOID)
-        {
-            /* backwards-compatibility hack */
-            ereport(WARNING,
-                    (errmsg("changing return type of function %s from %s to %s",
-                            NameListToString(inputName), "opaque", typeName)));
-            SetFunctionReturnType(inputOid, typoid);
-        }
-        else
-            ereport(ERROR,
-                    (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-                     errmsg("type input function %s must return type %s",
-                            NameListToString(inputName), typeName)));
-    }
-    resulttype = get_func_rettype(outputOid);
-    if (resulttype != CSTRINGOID)
-    {
-        if (resulttype == OPAQUEOID)
-        {
-            /* backwards-compatibility hack */
-            ereport(WARNING,
-                    (errmsg("changing return type of function %s from %s to %s",
-                            NameListToString(outputName), "opaque", "cstring")));
-            SetFunctionReturnType(outputOid, CSTRINGOID);
-        }
-        else
-            ereport(ERROR,
-                    (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-                     errmsg("type output function %s must return type %s",
-                            NameListToString(outputName), "cstring")));
-    }
-    if (receiveOid)
-    {
-        resulttype = get_func_rettype(receiveOid);
-        if (resulttype != typoid)
-            ereport(ERROR,
-                    (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-                     errmsg("type receive function %s must return type %s",
-                            NameListToString(receiveName), typeName)));
-    }
-    if (sendOid)
-    {
-        resulttype = get_func_rettype(sendOid);
-        if (resulttype != BYTEAOID)
-            ereport(ERROR,
-                    (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-                     errmsg("type send function %s must return type %s",
-                            NameListToString(sendName), "bytea")));
-    }
-
-    /*
      * Convert typmodin/out function proc names to OIDs.
      */
     if (typmodinName)
@@ -1404,16 +1347,9 @@ DefineRange(CreateRangeStmt *stmt)
     }

     /*
-     * If it doesn't exist, create it as a shell, so that the OID is known for
-     * use in the range function definitions.
+     * Unlike DefineType(), we don't insist on a shell type existing first, as
+     * it's only needed if the user wants to specify a canonical function.
      */
-    if (!OidIsValid(typoid))
-    {
-        address = TypeShellMake(typeName, typeNamespace, GetUserId());
-        typoid = address.objectId;
-        /* Make new shell type visible for modification below */
-        CommandCounterIncrement();
-    }

     /* Extract the parameters from the parameter list */
     foreach(lc, stmt->params)
@@ -1502,8 +1438,15 @@ DefineRange(CreateRangeStmt *stmt)

     /* Identify support functions, if provided */
     if (rangeCanonicalName != NIL)
+    {
+        if (!OidIsValid(typoid))
+            ereport(ERROR,
+                    (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                     errmsg("cannot specify a canonical function without a pre-created shell type"),
+                     errhint("Create the type as a shell type, then create its canonicalization function, then do a
fullCREATE TYPE."))); 
         rangeCanonical = findRangeCanonicalFunction(rangeCanonicalName,
                                                     typoid);
+    }
     else
         rangeCanonical = InvalidOid;

@@ -1555,7 +1498,8 @@ DefineRange(CreateRangeStmt *stmt)
                    0,            /* Array dimensions of typbasetype */
                    false,        /* Type NOT NULL */
                    InvalidOid); /* type's collation (ranges never have one) */
-    Assert(typoid == address.objectId);
+    Assert(typoid == InvalidOid || typoid == address.objectId);
+    typoid = address.objectId;

     /* Create the entry in pg_range */
     RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
@@ -1695,63 +1639,32 @@ findTypeInputFunction(List *procname, Oid typeOid)

     /*
      * Input functions can take a single argument of type CSTRING, or three
-     * arguments (string, typioparam OID, typmod).
-     *
-     * For backwards compatibility we allow OPAQUE in place of CSTRING; if we
-     * see this, we issue a warning and fix up the pg_proc entry.
+     * arguments (string, typioparam OID, typmod).  They must return the
+     * target type.
      */
     argList[0] = CSTRINGOID;

     procOid = LookupFuncName(procname, 1, argList, true);
-    if (OidIsValid(procOid))
-        return procOid;
-
-    argList[1] = OIDOID;
-    argList[2] = INT4OID;
-
-    procOid = LookupFuncName(procname, 3, argList, true);
-    if (OidIsValid(procOid))
-        return procOid;
-
-    /* No luck, try it with OPAQUE */
-    argList[0] = OPAQUEOID;
-
-    procOid = LookupFuncName(procname, 1, argList, true);
-
     if (!OidIsValid(procOid))
     {
         argList[1] = OIDOID;
         argList[2] = INT4OID;

         procOid = LookupFuncName(procname, 3, argList, true);
+        if (!OidIsValid(procOid))
+            ereport(ERROR,
+                    (errcode(ERRCODE_UNDEFINED_FUNCTION),
+                     errmsg("function %s does not exist",
+                            func_signature_string(procname, 1, NIL, argList))));
     }

-    if (OidIsValid(procOid))
-    {
-        /* Found, but must complain and fix the pg_proc entry */
-        ereport(WARNING,
-                (errmsg("changing argument type of function %s from \"opaque\" to \"cstring\"",
-                        NameListToString(procname))));
-        SetFunctionArgType(procOid, 0, CSTRINGOID);
-
-        /*
-         * Need CommandCounterIncrement since DefineType will likely try to
-         * alter the pg_proc tuple again.
-         */
-        CommandCounterIncrement();
-
-        return procOid;
-    }
-
-    /* Use CSTRING (preferred) in the error message */
-    argList[0] = CSTRINGOID;
-
-    ereport(ERROR,
-            (errcode(ERRCODE_UNDEFINED_FUNCTION),
-             errmsg("function %s does not exist",
-                    func_signature_string(procname, 1, NIL, argList))));
+    if (get_func_rettype(procOid) != typeOid)
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                 errmsg("type input function %s must return type %s",
+                        NameListToString(procname), format_type_be(typeOid))));

-    return InvalidOid;            /* keep compiler quiet */
+    return procOid;
 }

 static Oid
@@ -1761,48 +1674,25 @@ findTypeOutputFunction(List *procname, Oid typeOid)
     Oid            procOid;

     /*
-     * Output functions can take a single argument of the type.
-     *
-     * For backwards compatibility we allow OPAQUE in place of the actual type
-     * name; if we see this, we issue a warning and fix up the pg_proc entry.
+     * Output functions always take a single argument of the type and return
+     * cstring.
      */
     argList[0] = typeOid;

     procOid = LookupFuncName(procname, 1, argList, true);
-    if (OidIsValid(procOid))
-        return procOid;
-
-    /* No luck, try it with OPAQUE */
-    argList[0] = OPAQUEOID;
-
-    procOid = LookupFuncName(procname, 1, argList, true);
-
-    if (OidIsValid(procOid))
-    {
-        /* Found, but must complain and fix the pg_proc entry */
-        ereport(WARNING,
-                (errmsg("changing argument type of function %s from \"opaque\" to %s",
-                        NameListToString(procname), format_type_be(typeOid))));
-        SetFunctionArgType(procOid, 0, typeOid);
-
-        /*
-         * Need CommandCounterIncrement since DefineType will likely try to
-         * alter the pg_proc tuple again.
-         */
-        CommandCounterIncrement();
-
-        return procOid;
-    }
-
-    /* Use type name, not OPAQUE, in the failure message. */
-    argList[0] = typeOid;
+    if (!OidIsValid(procOid))
+        ereport(ERROR,
+                (errcode(ERRCODE_UNDEFINED_FUNCTION),
+                 errmsg("function %s does not exist",
+                        func_signature_string(procname, 1, NIL, argList))));

-    ereport(ERROR,
-            (errcode(ERRCODE_UNDEFINED_FUNCTION),
-             errmsg("function %s does not exist",
-                    func_signature_string(procname, 1, NIL, argList))));
+    if (get_func_rettype(procOid) != CSTRINGOID)
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                 errmsg("type output function %s must return type %s",
+                        NameListToString(procname), "cstring")));

-    return InvalidOid;            /* keep compiler quiet */
+    return procOid;
 }

 static Oid
@@ -1813,27 +1703,32 @@ findTypeReceiveFunction(List *procname, Oid typeOid)

     /*
      * Receive functions can take a single argument of type INTERNAL, or three
-     * arguments (internal, typioparam OID, typmod).
+     * arguments (internal, typioparam OID, typmod).  They must return the
+     * target type.
      */
     argList[0] = INTERNALOID;

     procOid = LookupFuncName(procname, 1, argList, true);
-    if (OidIsValid(procOid))
-        return procOid;
-
-    argList[1] = OIDOID;
-    argList[2] = INT4OID;
+    if (!OidIsValid(procOid))
+    {
+        argList[1] = OIDOID;
+        argList[2] = INT4OID;

-    procOid = LookupFuncName(procname, 3, argList, true);
-    if (OidIsValid(procOid))
-        return procOid;
+        procOid = LookupFuncName(procname, 3, argList, true);
+        if (!OidIsValid(procOid))
+            ereport(ERROR,
+                    (errcode(ERRCODE_UNDEFINED_FUNCTION),
+                     errmsg("function %s does not exist",
+                            func_signature_string(procname, 1, NIL, argList))));
+    }

-    ereport(ERROR,
-            (errcode(ERRCODE_UNDEFINED_FUNCTION),
-             errmsg("function %s does not exist",
-                    func_signature_string(procname, 1, NIL, argList))));
+    if (get_func_rettype(procOid) != typeOid)
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                 errmsg("type receive function %s must return type %s",
+                        NameListToString(procname), format_type_be(typeOid))));

-    return InvalidOid;            /* keep compiler quiet */
+    return procOid;
 }

 static Oid
@@ -1843,20 +1738,25 @@ findTypeSendFunction(List *procname, Oid typeOid)
     Oid            procOid;

     /*
-     * Send functions can take a single argument of the type.
+     * Send functions always take a single argument of the type and return
+     * bytea.
      */
     argList[0] = typeOid;

     procOid = LookupFuncName(procname, 1, argList, true);
-    if (OidIsValid(procOid))
-        return procOid;
+    if (!OidIsValid(procOid))
+        ereport(ERROR,
+                (errcode(ERRCODE_UNDEFINED_FUNCTION),
+                 errmsg("function %s does not exist",
+                        func_signature_string(procname, 1, NIL, argList))));

-    ereport(ERROR,
-            (errcode(ERRCODE_UNDEFINED_FUNCTION),
-             errmsg("function %s does not exist",
-                    func_signature_string(procname, 1, NIL, argList))));
+    if (get_func_rettype(procOid) != BYTEAOID)
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                 errmsg("type send function %s must return type %s",
+                        NameListToString(procname), "bytea")));

-    return InvalidOid;            /* keep compiler quiet */
+    return procOid;
 }

 static Oid
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dede9d7..0992c23 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -55,7 +55,6 @@ extern Oid    ResolveOpClass(List *opclass, Oid attrType,
 extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
 extern void RemoveFunctionById(Oid funcOid);
 extern void SetFunctionReturnType(Oid funcOid, Oid newRetType);
-extern void SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType);
 extern ObjectAddress AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt);
 extern ObjectAddress CreateCast(CreateCastStmt *stmt);
 extern void DropCastById(Oid castOid);
diff --git a/src/test/regress/expected/create_type.out b/src/test/regress/expected/create_type.out
index 8309756..eb55e25 100644
--- a/src/test/regress/expected/create_type.out
+++ b/src/test/regress/expected/create_type.out
@@ -83,8 +83,10 @@ SELECT * FROM default_test;
  zippo | 42
 (1 row)

+-- We need a shell type to test some CREATE TYPE failure cases with
+CREATE TYPE bogus_type;
 -- invalid: non-lowercase quoted identifiers
-CREATE TYPE case_int42 (
+CREATE TYPE bogus_type (
     "Internallength" = 4,
     "Input" = int42_in,
     "Output" = int42_out,
@@ -111,6 +113,20 @@ WARNING:  type attribute "Passedbyvalue" not recognized
 LINE 7:  "Passedbyvalue"
          ^
 ERROR:  type input function must be specified
+-- invalid: input/output function incompatibility
+CREATE TYPE bogus_type (INPUT = array_in,
+    OUTPUT = array_out,
+    ELEMENT = int,
+    INTERNALLENGTH = 32);
+ERROR:  type input function array_in must return type bogus_type
+DROP TYPE bogus_type;
+-- It no longer is possible to issue CREATE TYPE without making a shell first
+CREATE TYPE bogus_type (INPUT = array_in,
+    OUTPUT = array_out,
+    ELEMENT = int,
+    INTERNALLENGTH = 32);
+ERROR:  type "bogus_type" does not exist
+HINT:  Create the type as a shell type, then create its I/O functions, then do a full CREATE TYPE.
 -- Test stand-alone composite type
 CREATE TYPE default_test_row AS (f1 text_w_default, f2 int42);
 CREATE FUNCTION get_default_test() RETURNS SETOF default_test_row AS '
@@ -137,28 +153,25 @@ ERROR:  type "text_w_default" already exists
 DROP TYPE default_test_row CASCADE;
 NOTICE:  drop cascades to function get_default_test()
 DROP TABLE default_test;
--- Check type create with input/output incompatibility
-CREATE TYPE not_existing_type (INPUT = array_in,
-    OUTPUT = array_out,
-    ELEMENT = int,
-    INTERNALLENGTH = 32);
-ERROR:  function array_out(not_existing_type) does not exist
--- Check dependency transfer of opaque functions when creating a new type
-CREATE FUNCTION base_fn_in(cstring) RETURNS opaque AS 'boolin'
+-- Check dependencies are established when creating a new type
+CREATE TYPE base_type;
+CREATE FUNCTION base_fn_in(cstring) RETURNS base_type AS 'boolin'
     LANGUAGE internal IMMUTABLE STRICT;
-CREATE FUNCTION base_fn_out(opaque) RETURNS opaque AS 'boolout'
+NOTICE:  return type base_type is only a shell
+CREATE FUNCTION base_fn_out(base_type) RETURNS cstring AS 'boolout'
     LANGUAGE internal IMMUTABLE STRICT;
+NOTICE:  argument type base_type is only a shell
 CREATE TYPE base_type(INPUT = base_fn_in, OUTPUT = base_fn_out);
-WARNING:  changing argument type of function base_fn_out from "opaque" to base_type
-WARNING:  changing return type of function base_fn_in from opaque to base_type
-WARNING:  changing return type of function base_fn_out from opaque to cstring
 DROP FUNCTION base_fn_in(cstring); -- error
 ERROR:  cannot drop function base_fn_in(cstring) because other objects depend on it
 DETAIL:  type base_type depends on function base_fn_in(cstring)
 function base_fn_out(base_type) depends on type base_type
 HINT:  Use DROP ... CASCADE to drop the dependent objects too.
-DROP FUNCTION base_fn_out(opaque); -- error
-ERROR:  function base_fn_out(opaque) does not exist
+DROP FUNCTION base_fn_out(base_type); -- error
+ERROR:  cannot drop function base_fn_out(base_type) because other objects depend on it
+DETAIL:  type base_type depends on function base_fn_out(base_type)
+function base_fn_in(cstring) depends on type base_type
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
 DROP TYPE base_type; -- error
 ERROR:  cannot drop type base_type because other objects depend on it
 DETAIL:  function base_fn_in(cstring) depends on type base_type
diff --git a/src/test/regress/sql/create_type.sql b/src/test/regress/sql/create_type.sql
index 3d1deba..68b04fd 100644
--- a/src/test/regress/sql/create_type.sql
+++ b/src/test/regress/sql/create_type.sql
@@ -84,8 +84,11 @@ INSERT INTO default_test DEFAULT VALUES;

 SELECT * FROM default_test;

+-- We need a shell type to test some CREATE TYPE failure cases with
+CREATE TYPE bogus_type;
+
 -- invalid: non-lowercase quoted identifiers
-CREATE TYPE case_int42 (
+CREATE TYPE bogus_type (
     "Internallength" = 4,
     "Input" = int42_in,
     "Output" = int42_out,
@@ -94,6 +97,20 @@ CREATE TYPE case_int42 (
     "Passedbyvalue"
 );

+-- invalid: input/output function incompatibility
+CREATE TYPE bogus_type (INPUT = array_in,
+    OUTPUT = array_out,
+    ELEMENT = int,
+    INTERNALLENGTH = 32);
+
+DROP TYPE bogus_type;
+
+-- It no longer is possible to issue CREATE TYPE without making a shell first
+CREATE TYPE bogus_type (INPUT = array_in,
+    OUTPUT = array_out,
+    ELEMENT = int,
+    INTERNALLENGTH = 32);
+
 -- Test stand-alone composite type

 CREATE TYPE default_test_row AS (f1 text_w_default, f2 int42);
@@ -119,20 +136,15 @@ DROP TYPE default_test_row CASCADE;

 DROP TABLE default_test;

--- Check type create with input/output incompatibility
-CREATE TYPE not_existing_type (INPUT = array_in,
-    OUTPUT = array_out,
-    ELEMENT = int,
-    INTERNALLENGTH = 32);
-
--- Check dependency transfer of opaque functions when creating a new type
-CREATE FUNCTION base_fn_in(cstring) RETURNS opaque AS 'boolin'
+-- Check dependencies are established when creating a new type
+CREATE TYPE base_type;
+CREATE FUNCTION base_fn_in(cstring) RETURNS base_type AS 'boolin'
     LANGUAGE internal IMMUTABLE STRICT;
-CREATE FUNCTION base_fn_out(opaque) RETURNS opaque AS 'boolout'
+CREATE FUNCTION base_fn_out(base_type) RETURNS cstring AS 'boolout'
     LANGUAGE internal IMMUTABLE STRICT;
 CREATE TYPE base_type(INPUT = base_fn_in, OUTPUT = base_fn_out);
 DROP FUNCTION base_fn_in(cstring); -- error
-DROP FUNCTION base_fn_out(opaque); -- error
+DROP FUNCTION base_fn_out(base_type); -- error
 DROP TYPE base_type; -- error
 DROP TYPE base_type CASCADE;

diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index d1d0331..410eaed 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -4827,10 +4827,6 @@ SELECT * FROM pg_attribute
     <primary>unknown</primary>
    </indexterm>

-   <indexterm zone="datatype-pseudo">
-    <primary>opaque</primary>
-   </indexterm>
-
    <para>
     The <productname>PostgreSQL</productname> type system contains a
     number of special-purpose entries that are collectively called
@@ -4953,12 +4949,6 @@ SELECT * FROM pg_attribute
         <entry>Identifies a not-yet-resolved type, e.g. of an undecorated
          string literal.</entry>
        </row>
-
-       <row>
-        <entry><type>opaque</type></entry>
-        <entry>An obsolete type name that formerly served many of the above
-         purposes.</entry>
-       </row>
       </tbody>
      </tgroup>
     </table>
diff --git a/doc/src/sgml/ref/create_language.sgml b/doc/src/sgml/ref/create_language.sgml
index af9d115..2243ee6 100644
--- a/doc/src/sgml/ref/create_language.sgml
+++ b/doc/src/sgml/ref/create_language.sgml
@@ -211,16 +211,6 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
    database, which will cause it to be available automatically in
    all subsequently-created databases.
   </para>
-
-  <para>
-   In <productname>PostgreSQL</productname> versions before 7.3, it was
-   necessary to declare handler functions as returning the placeholder
-   type <type>opaque</type>, rather than <type>language_handler</type>.
-   To support loading
-   of old dump files, <command>CREATE LANGUAGE</command> will accept a function
-   declared as returning <type>opaque</type>, but it will issue a notice and
-   change the function's declared return type to <type>language_handler</type>.
-  </para>
  </refsect1>

  <refsect1 id="sql-createlanguage-examples">
diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml
index 3339a4b..3b8f25e 100644
--- a/doc/src/sgml/ref/create_trigger.sgml
+++ b/doc/src/sgml/ref/create_trigger.sgml
@@ -543,15 +543,6 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
    row-level triggers with transition relations cannot be defined on
    partitions or inheritance child tables.
   </para>
-
-  <para>
-   In <productname>PostgreSQL</productname> versions before 7.3, it was
-   necessary to declare trigger functions as returning the placeholder
-   type <type>opaque</type>, rather than <type>trigger</type>.  To support loading
-   of old dump files, <command>CREATE TRIGGER</command> will accept a function
-   declared as returning <type>opaque</type>, but it will issue a notice and
-   change the function's declared return type to <type>trigger</type>.
-  </para>
  </refsect1>

  <refsect1 id="sql-createtrigger-examples">
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 9279c05..e634ccf 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -1399,49 +1399,6 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
     return address;
 }

-/*
- * SetFunctionReturnType - change declared return type of a function
- *
- * This is presently only used for adjusting legacy functions that return
- * OPAQUE to return whatever we find their correct definition should be.
- * The caller should emit a suitable warning explaining what we did.
- */
-void
-SetFunctionReturnType(Oid funcOid, Oid newRetType)
-{
-    Relation    pg_proc_rel;
-    HeapTuple    tup;
-    Form_pg_proc procForm;
-    ObjectAddress func_address;
-    ObjectAddress type_address;
-
-    pg_proc_rel = table_open(ProcedureRelationId, RowExclusiveLock);
-
-    tup = SearchSysCacheCopy1(PROCOID, ObjectIdGetDatum(funcOid));
-    if (!HeapTupleIsValid(tup)) /* should not happen */
-        elog(ERROR, "cache lookup failed for function %u", funcOid);
-    procForm = (Form_pg_proc) GETSTRUCT(tup);
-
-    if (procForm->prorettype != OPAQUEOID)    /* caller messed up */
-        elog(ERROR, "function %u doesn't return OPAQUE", funcOid);
-
-    /* okay to overwrite copied tuple */
-    procForm->prorettype = newRetType;
-
-    /* update the catalog and its indexes */
-    CatalogTupleUpdate(pg_proc_rel, &tup->t_self, tup);
-
-    table_close(pg_proc_rel, RowExclusiveLock);
-
-    /*
-     * Also update the dependency to the new type. Opaque is a pinned type, so
-     * there is no old dependency record for it that we would need to remove.
-     */
-    ObjectAddressSet(type_address, TypeRelationId, newRetType);
-    ObjectAddressSet(func_address, ProcedureRelationId, funcOid);
-    recordDependencyOn(&func_address, &type_address, DEPENDENCY_NORMAL);
-}
-

 /*
  * CREATE CAST
diff --git a/src/backend/commands/proclang.c b/src/backend/commands/proclang.c
index 9d72edb..21ceeb7 100644
--- a/src/backend/commands/proclang.c
+++ b/src/backend/commands/proclang.c
@@ -74,27 +74,10 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
     handlerOid = LookupFuncName(stmt->plhandler, 0, NULL, false);
     funcrettype = get_func_rettype(handlerOid);
     if (funcrettype != LANGUAGE_HANDLEROID)
-    {
-        /*
-         * We allow OPAQUE just so we can load old dump files.  When we see a
-         * handler function declared OPAQUE, change it to LANGUAGE_HANDLER.
-         * (This is probably obsolete and removable?)
-         */
-        if (funcrettype == OPAQUEOID)
-        {
-            ereport(WARNING,
-                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                     errmsg("changing return type of function %s from %s to %s",
-                            NameListToString(stmt->plhandler),
-                            "opaque", "language_handler")));
-            SetFunctionReturnType(handlerOid, LANGUAGE_HANDLEROID);
-        }
-        else
-            ereport(ERROR,
-                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                     errmsg("function %s must return type %s",
-                            NameListToString(stmt->plhandler), "language_handler")));
-    }
+        ereport(ERROR,
+                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                 errmsg("function %s must return type %s",
+                        NameListToString(stmt->plhandler), "language_handler")));

     /* validate the inline function */
     if (stmt->plinline)
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 6e8b722..056a912 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -699,25 +699,10 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
     }
     funcrettype = get_func_rettype(funcoid);
     if (funcrettype != TRIGGEROID)
-    {
-        /*
-         * We allow OPAQUE just so we can load old dump files.  When we see a
-         * trigger function declared OPAQUE, change it to TRIGGER.
-         */
-        if (funcrettype == OPAQUEOID)
-        {
-            ereport(WARNING,
-                    (errmsg("changing return type of function %s from %s to %s",
-                            NameListToString(stmt->funcname),
-                            "opaque", "trigger")));
-            SetFunctionReturnType(funcoid, TRIGGEROID);
-        }
-        else
-            ereport(ERROR,
-                    (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-                     errmsg("function %s must return type %s",
-                            NameListToString(stmt->funcname), "trigger")));
-    }
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                 errmsg("function %s must return type %s",
+                        NameListToString(stmt->funcname), "trigger")));

     /*
      * If the command is a user-entered CREATE CONSTRAINT TRIGGER command that
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index ad0e363..4653fc3 100644
--- a/src/backend/utils/adt/pseudotypes.c
+++ b/src/backend/utils/adt/pseudotypes.c
@@ -415,7 +415,6 @@ PSEUDOTYPE_DUMMY_IO_FUNCS(fdw_handler);
 PSEUDOTYPE_DUMMY_IO_FUNCS(index_am_handler);
 PSEUDOTYPE_DUMMY_IO_FUNCS(tsm_handler);
 PSEUDOTYPE_DUMMY_IO_FUNCS(internal);
-PSEUDOTYPE_DUMMY_IO_FUNCS(opaque);
 PSEUDOTYPE_DUMMY_IO_FUNCS(anyelement);
 PSEUDOTYPE_DUMMY_IO_FUNCS(anynonarray);
 PSEUDOTYPE_DUMMY_IO_FUNCS(table_am_handler);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ef15390..d3ab5bb 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -82,10 +82,9 @@ typedef struct

 typedef enum OidOptions
 {
-    zeroAsOpaque = 1,
-    zeroAsAny = 2,
-    zeroAsStar = 4,
-    zeroAsNone = 8
+    zeroIsError = 1,
+    zeroAsStar = 2,
+    zeroAsNone = 4
 } OidOptions;

 /* global decls */
@@ -122,8 +121,6 @@ static SimpleStringList tabledata_exclude_patterns = {NULL, NULL};
 static SimpleOidList tabledata_exclude_oids = {NULL, NULL};


-char        g_opaque_type[10];    /* name for the opaque type */
-
 /* placeholders for the delimiters for comments */
 char        g_comment_start[10];
 char        g_comment_end[10];
@@ -404,7 +401,6 @@ main(int argc, char **argv)

     strcpy(g_comment_start, "-- ");
     g_comment_end[0] = '\0';
-    strcpy(g_opaque_type, "opaque");

     progname = get_progname(argv[0]);

@@ -10736,7 +10732,7 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo)
     {
         char       *elemType;

-        elemType = getFormattedTypeName(fout, tyinfo->typelem, zeroAsOpaque);
+        elemType = getFormattedTypeName(fout, tyinfo->typelem, zeroIsError);
         appendPQExpBuffer(q, ",\n    ELEMENT = %s", elemType);
         free(elemType);
     }
@@ -11547,7 +11543,7 @@ format_function_arguments_old(Archive *fout,
         const char *argname;

         typid = allargtypes ? atooid(allargtypes[j]) : finfo->argtypes[j];
-        typname = getFormattedTypeName(fout, typid, zeroAsOpaque);
+        typname = getFormattedTypeName(fout, typid, zeroIsError);

         if (argmodes)
         {
@@ -11616,7 +11612,7 @@ format_function_signature(Archive *fout, FuncInfo *finfo, bool honor_quotes)
             appendPQExpBufferStr(&fn, ", ");

         typname = getFormattedTypeName(fout, finfo->argtypes[j],
-                                       zeroAsOpaque);
+                                       zeroIsError);
         appendPQExpBufferStr(&fn, typname);
         free(typname);
     }
@@ -12021,7 +12017,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
     else
     {
         rettypename = getFormattedTypeName(fout, finfo->prorettype,
-                                           zeroAsOpaque);
+                                           zeroIsError);
         appendPQExpBuffer(q, " RETURNS %s%s",
                           (proretset[0] == 't') ? "SETOF " : "",
                           rettypename);
@@ -13740,7 +13736,7 @@ format_aggregate_signature(AggInfo *agginfo, Archive *fout, bool honor_quotes)
             char       *typname;

             typname = getFormattedTypeName(fout, agginfo->aggfn.argtypes[j],
-                                           zeroAsOpaque);
+                                           zeroIsError);

             appendPQExpBuffer(&buf, "%s%s",
                               (j > 0) ? ", " : "",
@@ -18363,11 +18359,7 @@ getFormattedTypeName(Archive *fout, Oid oid, OidOptions opts)

     if (oid == 0)
     {
-        if ((opts & zeroAsOpaque) != 0)
-            return pg_strdup(g_opaque_type);
-        else if ((opts & zeroAsAny) != 0)
-            return pg_strdup("'any'");
-        else if ((opts & zeroAsStar) != 0)
+        if ((opts & zeroAsStar) != 0)
             return pg_strdup("*");
         else if ((opts & zeroAsNone) != 0)
             return pg_strdup("NONE");
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 21004e5..852020b 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -640,8 +640,6 @@ typedef struct _extensionMemberId
 extern char g_comment_start[10];
 extern char g_comment_end[10];

-extern char g_opaque_type[10];    /* name for the opaque type */
-
 /*
  *    common utility functions
  */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 07a86c7..7fb574f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7054,12 +7054,6 @@
 { oid => '2305', descr => 'I/O',
   proname => 'internal_out', prorettype => 'cstring', proargtypes => 'internal',
   prosrc => 'internal_out' },
-{ oid => '2306', descr => 'I/O',
-  proname => 'opaque_in', proisstrict => 'f', prorettype => 'opaque',
-  proargtypes => 'cstring', prosrc => 'opaque_in' },
-{ oid => '2307', descr => 'I/O',
-  proname => 'opaque_out', prorettype => 'cstring', proargtypes => 'opaque',
-  prosrc => 'opaque_out' },
 { oid => '2312', descr => 'I/O',
   proname => 'anyelement_in', prorettype => 'anyelement',
   proargtypes => 'cstring', prosrc => 'anyelement_in' },
@@ -7067,10 +7061,10 @@
   proname => 'anyelement_out', prorettype => 'cstring',
   proargtypes => 'anyelement', prosrc => 'anyelement_out' },
 { oid => '2398', descr => 'I/O',
-  proname => 'shell_in', proisstrict => 'f', prorettype => 'opaque',
+  proname => 'shell_in', proisstrict => 'f', prorettype => 'void',
   proargtypes => 'cstring', prosrc => 'shell_in' },
 { oid => '2399', descr => 'I/O',
-  proname => 'shell_out', prorettype => 'cstring', proargtypes => 'opaque',
+  proname => 'shell_out', prorettype => 'cstring', proargtypes => 'void',
   prosrc => 'shell_out' },
 { oid => '2597', descr => 'I/O',
   proname => 'domain_in', proisstrict => 'f', provolatile => 's',
diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index 4cf2b9d..b00597d 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -546,10 +546,6 @@
   typtype => 'p', typcategory => 'P', typinput => 'internal_in',
   typoutput => 'internal_out', typreceive => '-', typsend => '-',
   typalign => 'ALIGNOF_POINTER' },
-{ oid => '2282', descr => 'obsolete, deprecated pseudo-type',
-  typname => 'opaque', typlen => '4', typbyval => 't', typtype => 'p',
-  typcategory => 'P', typinput => 'opaque_in', typoutput => 'opaque_out',
-  typreceive => '-', typsend => '-', typalign => 'i' },
 { oid => '2283', descr => 'pseudo-type representing a polymorphic base type',
   typname => 'anyelement', typlen => '4', typbyval => 't', typtype => 'p',
   typcategory => 'P', typinput => 'anyelement_in',
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 0992c23..5cd6975 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -54,7 +54,6 @@ extern Oid    ResolveOpClass(List *opclass, Oid attrType,
 /* commands/functioncmds.c */
 extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
 extern void RemoveFunctionById(Oid funcOid);
-extern void SetFunctionReturnType(Oid funcOid, Oid newRetType);
 extern ObjectAddress AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt);
 extern ObjectAddress CreateCast(CreateCastStmt *stmt);
 extern void DropCastById(Oid castOid);
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index a65bce0..5fdf303 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -2000,9 +2000,7 @@ plperl_validator(PG_FUNCTION_ARGS)
     /* except for TRIGGER, EVTTRIGGER, RECORD, or VOID */
     if (functyptype == TYPTYPE_PSEUDO)
     {
-        /* we assume OPAQUE with no arguments means a trigger */
-        if (proc->prorettype == TRIGGEROID ||
-            (proc->prorettype == OPAQUEOID && proc->pronargs == 0))
+        if (proc->prorettype == TRIGGEROID)
             is_trigger = true;
         else if (proc->prorettype == EVTTRIGGEROID)
             is_event_trigger = true;
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index b83087e..b434818 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -421,12 +421,10 @@ plpgsql_validator(PG_FUNCTION_ARGS)
     functyptype = get_typtype(proc->prorettype);

     /* Disallow pseudotype result */
-    /* except for TRIGGER, RECORD, VOID, or polymorphic */
+    /* except for TRIGGER, EVTTRIGGER, RECORD, VOID, or polymorphic */
     if (functyptype == TYPTYPE_PSEUDO)
     {
-        /* we assume OPAQUE with no arguments means a trigger */
-        if (proc->prorettype == TRIGGEROID ||
-            (proc->prorettype == OPAQUEOID && proc->pronargs == 0))
+        if (proc->prorettype == TRIGGEROID)
             is_dml_trigger = true;
         else if (proc->prorettype == EVTTRIGGEROID)
             is_event_trigger = true;
diff --git a/src/pl/plpython/plpy_main.c b/src/pl/plpython/plpy_main.c
index 882d69e..3eedaa8 100644
--- a/src/pl/plpython/plpy_main.c
+++ b/src/pl/plpython/plpy_main.c
@@ -379,9 +379,7 @@ plpython2_inline_handler(PG_FUNCTION_ARGS)
 static bool
 PLy_procedure_is_trigger(Form_pg_proc procStruct)
 {
-    return (procStruct->prorettype == TRIGGEROID ||
-            (procStruct->prorettype == OPAQUEOID &&
-             procStruct->pronargs == 0));
+    return (procStruct->prorettype == TRIGGEROID);
 }

 static void
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index fb6c029..40468e8 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -384,7 +384,7 @@ FROM pg_proc as p1
 WHERE  p1.prorettype = 'cstring'::regtype
     AND NOT EXISTS(SELECT 1 FROM pg_type WHERE typoutput = p1.oid)
     AND NOT EXISTS(SELECT 1 FROM pg_type WHERE typmodout = p1.oid)
-    AND p1.oid != 'shell_out(opaque)'::regprocedure
+    AND p1.oid != 'shell_out(void)'::regprocedure
 ORDER BY 1;
  oid  |   proname
 ------+--------------
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 8351b64..f06f245 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -309,7 +309,7 @@ FROM pg_proc as p1
 WHERE  p1.prorettype = 'cstring'::regtype
     AND NOT EXISTS(SELECT 1 FROM pg_type WHERE typoutput = p1.oid)
     AND NOT EXISTS(SELECT 1 FROM pg_type WHERE typmodout = p1.oid)
-    AND p1.oid != 'shell_out(opaque)'::regprocedure
+    AND p1.oid != 'shell_out(void)'::regprocedure
 ORDER BY 1;

 -- Check for length inconsistencies between the various argument-info arrays.
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;

pgsql-hackers by date:

Previous
From: Tom Lane
Date:
Subject: Re: Allowing ALTER TYPE to change storage strategy
Next
From: "David G. Johnston"
Date:
Subject: Re: Allowing ALTER TYPE to change storage strategy