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 13399.1583448404@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:
> If not, we probably should bite the bullet and go for #1, since
> I have little doubt that we'll need that someday anyway.
> The trick will be to keep down the cache invalidation overhead...

Here's a version that does it like that.  I'm less worried about the
overhead than I was before, because I realized that we already had
a syscache callback for pg_type there.  And it was being pretty
stupid about which entries it reset, too, so this version might
actually net out as less overhead (in some workloads anyway).

For ease of review I just added the new TCFLAGS value out of
sequence, but I'd be inclined to renumber the bits back into
sequence before committing.

            regards, tom lane

diff --git a/doc/src/sgml/ref/alter_type.sgml b/doc/src/sgml/ref/alter_type.sgml
index 67be1dd..c20ed99 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,84 @@ 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
+         from <literal>plain</literal> to another setting requires superuser
+         privilege (because it requires that the type's C functions all be
+         TOAST-ready), and changing to <literal>plain</literal> from another
+         setting is not allowed at all (since the type may already have
+         TOASTed values present in the database).  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 +235,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 +432,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..b088ca8 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,351 @@ 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.
+ */
+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")));
+
+            /*
+             * Switching from PLAIN to non-PLAIN is allowed, but it requires
+             * superuser, since we can't validate that the type's C functions
+             * will support it.  Switching from non-PLAIN to PLAIN is
+             * disallowed outright, because it's not practical to ensure that
+             * no tables have toasted values of the type.  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)
+                requireSuper = true;
+            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/cache/typcache.c b/src/backend/utils/cache/typcache.c
index cdf6331..4213ecf 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -23,11 +23,12 @@
  * permanently allows caching pointers to them in long-lived places.
  *
  * We have some provisions for updating cache entries if the stored data
- * becomes obsolete.  Information dependent on opclasses is cleared if we
- * detect updates to pg_opclass.  We also support clearing the tuple
- * descriptor and operator/function parts of a rowtype's cache entry,
- * since those may need to change as a consequence of ALTER TABLE.
- * Domain constraint changes are also tracked properly.
+ * becomes obsolete.  Core data extracted from the pg_type row is updated
+ * when we detect updates to pg_type.  Information dependent on opclasses is
+ * cleared if we detect updates to pg_opclass.  We also support clearing the
+ * tuple descriptor and operator/function parts of a rowtype's cache entry,
+ * since those may need to change as a consequence of ALTER TABLE.  Domain
+ * constraint changes are also tracked properly.
  *
  *
  * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
@@ -80,6 +81,7 @@ static HTAB *TypeCacheHash = NULL;
 static TypeCacheEntry *firstDomainTypeEntry = NULL;

 /* Private flag bits in the TypeCacheEntry.flags field */
+#define TCFLAGS_HAVE_PG_TYPE_DATA            0x040000
 #define TCFLAGS_CHECKED_BTREE_OPCLASS        0x000001
 #define TCFLAGS_CHECKED_HASH_OPCLASS        0x000002
 #define TCFLAGS_CHECKED_EQ_OPR                0x000004
@@ -99,6 +101,12 @@ static TypeCacheEntry *firstDomainTypeEntry = NULL;
 #define TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS    0x010000
 #define TCFLAGS_DOMAIN_BASE_IS_COMPOSITE    0x020000

+/* The flags associated with equality/comparison/hashing are all but these: */
+#define TCFLAGS_OPERATOR_FLAGS \
+    (~(TCFLAGS_HAVE_PG_TYPE_DATA | \
+       TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS | \
+       TCFLAGS_DOMAIN_BASE_IS_COMPOSITE))
+
 /*
  * Data stored about a domain type's constraints.  Note that we do not create
  * this struct for the common case of a constraint-less domain; we just set
@@ -295,6 +303,7 @@ static bool range_element_has_hashing(TypeCacheEntry *typentry);
 static bool range_element_has_extended_hashing(TypeCacheEntry *typentry);
 static void cache_range_element_properties(TypeCacheEntry *typentry);
 static void TypeCacheRelCallback(Datum arg, Oid relid);
+static void TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void TypeCacheConstrCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void load_enum_cache_data(TypeCacheEntry *tcache);
@@ -337,9 +346,9 @@ lookup_type_cache(Oid type_id, int flags)

         /* Also set up callbacks for SI invalidations */
         CacheRegisterRelcacheCallback(TypeCacheRelCallback, (Datum) 0);
+        CacheRegisterSyscacheCallback(TYPEOID, TypeCacheTypCallback, (Datum) 0);
         CacheRegisterSyscacheCallback(CLAOID, TypeCacheOpcCallback, (Datum) 0);
         CacheRegisterSyscacheCallback(CONSTROID, TypeCacheConstrCallback, (Datum) 0);
-        CacheRegisterSyscacheCallback(TYPEOID, TypeCacheConstrCallback, (Datum) 0);

         /* Also make sure CacheMemoryContext exists */
         if (!CacheMemoryContext)
@@ -381,7 +390,13 @@ lookup_type_cache(Oid type_id, int flags)
         Assert(!found);            /* it wasn't there a moment ago */

         MemSet(typentry, 0, sizeof(TypeCacheEntry));
+
+        /* These fields can never change, by definition */
         typentry->type_id = type_id;
+        typentry->type_id_hash = GetSysCacheHashValue1(TYPEOID,
+                                                       ObjectIdGetDatum(type_id));
+
+        /* Keep this part in sync with the code below */
         typentry->typlen = typtup->typlen;
         typentry->typbyval = typtup->typbyval;
         typentry->typalign = typtup->typalign;
@@ -390,6 +405,7 @@ lookup_type_cache(Oid type_id, int flags)
         typentry->typrelid = typtup->typrelid;
         typentry->typelem = typtup->typelem;
         typentry->typcollation = typtup->typcollation;
+        typentry->flags |= TCFLAGS_HAVE_PG_TYPE_DATA;

         /* If it's a domain, immediately thread it into the domain cache list */
         if (typentry->typtype == TYPTYPE_DOMAIN)
@@ -400,6 +416,43 @@ lookup_type_cache(Oid type_id, int flags)

         ReleaseSysCache(tp);
     }
+    else if (!(typentry->flags & TCFLAGS_HAVE_PG_TYPE_DATA))
+    {
+        /*
+         * We have an entry, but its pg_type row got changed, so reload the
+         * data obtained directly from pg_type.
+         */
+        HeapTuple    tp;
+        Form_pg_type typtup;
+
+        tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_id));
+        if (!HeapTupleIsValid(tp))
+            ereport(ERROR,
+                    (errcode(ERRCODE_UNDEFINED_OBJECT),
+                     errmsg("type with OID %u does not exist", type_id)));
+        typtup = (Form_pg_type) GETSTRUCT(tp);
+        if (!typtup->typisdefined)
+            ereport(ERROR,
+                    (errcode(ERRCODE_UNDEFINED_OBJECT),
+                     errmsg("type \"%s\" is only a shell",
+                            NameStr(typtup->typname))));
+
+        /*
+         * Keep this part in sync with the code above.  Many of these fields
+         * shouldn't ever change, particularly typtype, but copy 'em anyway.
+         */
+        typentry->typlen = typtup->typlen;
+        typentry->typbyval = typtup->typbyval;
+        typentry->typalign = typtup->typalign;
+        typentry->typstorage = typtup->typstorage;
+        typentry->typtype = typtup->typtype;
+        typentry->typrelid = typtup->typrelid;
+        typentry->typelem = typtup->typelem;
+        typentry->typcollation = typtup->typcollation;
+        typentry->flags |= TCFLAGS_HAVE_PG_TYPE_DATA;
+
+        ReleaseSysCache(tp);
+    }

     /*
      * Look up opclasses if we haven't already and any dependent info is
@@ -750,12 +803,17 @@ lookup_type_cache(Oid type_id, int flags)

     /*
      * If requested, get information about a range type
+     *
+     * This includes making sure that the basic info about the range element
+     * type is up-to-date.
      */
     if ((flags & TYPECACHE_RANGE_INFO) &&
-        typentry->rngelemtype == NULL &&
         typentry->typtype == TYPTYPE_RANGE)
     {
-        load_rangetype_info(typentry);
+        if (typentry->rngelemtype == NULL)
+            load_rangetype_info(typentry);
+        else if (!(typentry->rngelemtype->flags & TCFLAGS_HAVE_PG_TYPE_DATA))
+            (void) lookup_type_cache(typentry->rngelemtype->type_id, 0);
     }

     /*
@@ -2129,7 +2187,7 @@ TypeCacheRelCallback(Datum arg, Oid relid)
             }

             /* Reset equality/comparison/hashing validity information */
-            typentry->flags = 0;
+            typentry->flags &= ~TCFLAGS_OPERATOR_FLAGS;
         }
         else if (typentry->typtype == TYPTYPE_DOMAIN)
         {
@@ -2140,7 +2198,39 @@ TypeCacheRelCallback(Datum arg, Oid relid)
              * type is composite, we don't need to reset anything.
              */
             if (typentry->flags & TCFLAGS_DOMAIN_BASE_IS_COMPOSITE)
-                typentry->flags = 0;
+                typentry->flags &= ~TCFLAGS_OPERATOR_FLAGS;
+        }
+    }
+}
+
+/*
+ * TypeCacheTypCallback
+ *        Syscache inval callback function
+ *
+ * This is called when a syscache invalidation event occurs for any
+ * pg_type row.  If we have information cached about that type, mark
+ * it as needing to be reloaded.
+ */
+static void
+TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue)
+{
+    HASH_SEQ_STATUS status;
+    TypeCacheEntry *typentry;
+
+    /* TypeCacheHash must exist, else this callback wouldn't be registered */
+    hash_seq_init(&status, TypeCacheHash);
+    while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL)
+    {
+        /* Is this the targeted type row (or it's a total cache flush)? */
+        if (hashvalue == 0 || typentry->type_id_hash == hashvalue)
+        {
+            /*
+             * Mark the data obtained directly from pg_type as invalid.  Also,
+             * if it's a domain, typnotnull might've changed, so we'll need to
+             * recalculate its constraints.
+             */
+            typentry->flags &= ~(TCFLAGS_HAVE_PG_TYPE_DATA |
+                                 TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS);
         }
     }
 }
@@ -2172,7 +2262,7 @@ TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue)
     while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL)
     {
         /* Reset equality/comparison/hashing validity information */
-        typentry->flags = 0;
+        typentry->flags &= ~TCFLAGS_OPERATOR_FLAGS;
     }
 }

@@ -2181,8 +2271,8 @@ TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue)
  *        Syscache inval callback function
  *
  * This is called when a syscache invalidation event occurs for any
- * pg_constraint or pg_type row.  We flush information about domain
- * constraints when this happens.
+ * pg_constraint row.  We flush information about domain constraints
+ * when this happens.
  *
  * It's slightly annoying that we can't tell whether the inval event was for a
  * domain constraint/type record or not; there's usually more update traffic
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..cdd20e5 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -33,6 +33,8 @@ typedef struct TypeCacheEntry
     /* typeId is the hash lookup key and MUST BE FIRST */
     Oid            type_id;        /* OID of the data type */

+    uint32        type_id_hash;    /* hashed value of the OID */
+
     /* some subsidiary information copied from the pg_type row */
     int16        typlen;
     bool        typbyval;
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: James Coleman
Date:
Subject: Re: [PATCH] Incremental sort (was: PoC: Partial sort)
Next
From: Tom Lane
Date:
Subject: Re: [PATCH] Incremental sort (was: PoC: Partial sort)