diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 34bc0d05266..c6f95fa6881 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7828,28 +7828,38 @@ SCRAM-SHA-256$<iteration count>:&l
types (those with typlen = -1) if
the type is prepared for toasting and what the default strategy
for attributes of this type should be.
- Possible values are
+ Possible values are:
- p: Value must always be stored plain.
+
+ p (plain): Values must always be stored plain
+ (non-varlena types always use this value).
+
- e: Value can be stored in a secondary
- relation (if relation has one, see
+ e (external): Values can be stored in a
+ secondary TOAST
relation (if relation has one, see
pg_class.reltoastrelid).
- m: Value can be stored compressed inline.
+
+ m (main): Values can be compressed and stored
+ inline.
+
- x: Value can be stored compressed inline or stored in secondary
storage.
+
+ x (extended): Values can be compressed and/or
+ moved to a secondary relation.
+
- Note that m columns can also be moved out to secondary
- storage, but only as a last resort (e and x columns are
- moved first).
+ x is the usual choice for toast-able types.
+ Note that m values can also be moved out to
+ secondary storage, but only as a last resort (e
+ and x values are moved first).
diff --git a/doc/src/sgml/ref/alter_type.sgml b/doc/src/sgml/ref/alter_type.sgml
index 67be1dd5683..e0afaf8d0b0 100644
--- a/doc/src/sgml/ref/alter_type.sgml
+++ b/doc/src/sgml/ref/alter_type.sgml
@@ -23,13 +23,14 @@ PostgreSQL documentation
-ALTER TYPE name action [, ... ]
ALTER TYPE name OWNER TO { new_owner | CURRENT_USER | SESSION_USER }
-ALTER TYPE name RENAME ATTRIBUTE attribute_name TO new_attribute_name [ CASCADE | RESTRICT ]
ALTER TYPE name RENAME TO new_name
ALTER TYPE name SET SCHEMA new_schema
+ALTER TYPE name RENAME ATTRIBUTE attribute_name TO new_attribute_name [ CASCADE | RESTRICT ]
+ALTER TYPE name action [, ... ]
ALTER TYPE name ADD VALUE [ IF NOT EXISTS ] new_enum_value [ { BEFORE | AFTER } neighbor_enum_value ]
ALTER TYPE name RENAME VALUE existing_enum_value TO new_enum_value
+ALTER TYPE name SET ( property = value [, ... ] )
where action is one of:
@@ -47,6 +48,43 @@ ALTER TYPE name RENAME VALUE
+
+ OWNER
+
+
+ This form changes the owner of the type.
+
+
+
+
+
+ RENAME
+
+
+ This form changes the name of the type.
+
+
+
+
+
+ SET SCHEMA
+
+
+ This form moves the type into another schema.
+
+
+
+
+
+ RENAME ATTRIBUTE
+
+
+ This form is only usable with composite types.
+ It changes the name of an individual attribute of the type.
+
+
+
+
ADD ATTRIBUTE
@@ -70,7 +108,7 @@ ALTER TYPE name RENAME VALUE
- SET DATA TYPE
+ ALTER ATTRIBUTE ... SET DATA TYPE
This form changes the type of an attribute of a composite type.
@@ -78,34 +116,6 @@ ALTER TYPE name RENAME VALUE
-
- OWNER
-
-
- This form changes the owner of the type.
-
-
-
-
-
- RENAME
-
-
- This form changes the name of the type or the name of an
- individual attribute of a composite type.
-
-
-
-
-
- SET SCHEMA
-
-
- This form moves the type into another schema.
-
-
-
-
ADD VALUE [ IF NOT EXISTS ] [ BEFORE | AFTER ]
@@ -135,6 +145,84 @@ ALTER TYPE name RENAME VALUE
+
+
+
+ SET ( property = value [, ... ] )
+
+
+
+ This form is only applicable to base types. It allows adjustment of a
+ subset of the base-type properties that can be set in CREATE
+ TYPE. Specifically, these properties can be changed:
+
+
+
+ RECEIVE can be set to the name of a binary input
+ function, or NONE to remove the type's binary
+ input function. Using this option requires superuser privilege.
+
+
+
+
+ SEND can be set to the name of a binary output
+ function, or NONE to remove the type's binary
+ output function. Using this option requires superuser privilege.
+
+
+
+
+ TYPMOD_IN can be set to the name of a type
+ modifier input function, or NONE to remove the
+ type's type modifier input function. Using this option requires
+ superuser privilege.
+
+
+
+
+ TYPMOD_OUT can be set to the name of a type
+ modifier output function, or NONE to remove the
+ type's type modifier output function. Using this option requires
+ superuser privilege.
+
+
+
+
+ ANALYZE can be set to the name of a type-specific
+ statistics collection function, or NONE to remove
+ the type's statistics collection function. Using this option
+ requires superuser privilege.
+
+
+
+
+ STORAGE
+ TOAST
+ per-type storage settings
+
+ can be set to plain,
+ extended, external,
+ or main (see for
+ more information about what these mean). However, changing
+ from plain to another setting requires superuser
+ privilege (because it requires that the type's C functions all be
+ TOAST-ready), and changing to plain 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 to change the TOAST
+ strategy for existing table columns.
+
+
+
+ See 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.
+
+
+
@@ -156,7 +244,7 @@ ALTER TYPE name RENAME VALUE USAGE privilege on the data type.
+ have USAGE privilege on the attribute's data type.
@@ -262,6 +350,16 @@ ALTER TYPE name RENAME VALUE
+
+ property
+
+
+ The name of a base-type property to be modified; see above for
+ possible values.
+
+
+
+
CASCADE
@@ -336,7 +434,7 @@ ALTER TYPE email SET SCHEMA customers;
- To add a new attribute to a type:
+ To add a new attribute to a composite type:
ALTER TYPE compfoo ADD ATTRIBUTE f3 int;
@@ -353,7 +451,20 @@ ALTER TYPE colors ADD VALUE 'orange' AFTER 'red';
To rename an enum value:
ALTER TYPE colors RENAME VALUE 'purple' TO 'mauve';
-
+
+
+
+
+ To create binary I/O functions for an existing base type:
+
+CREATE FUNCTION mytypesend(mytype) RETURNS bytea ...;
+CREATE FUNCTION mytyperecv(internal, oid, integer) RETURNS mytype ...;
+ALTER TYPE mytype SET (
+ SEND = mytypesend,
+ RECEIVE = mytyperecv
+);
+
+
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index 56e0bcf39ee..cd567149689 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 d732a3af450..b088ca848d3 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));
@@ -492,47 +516,6 @@ DefineType(ParseState *pstate, List *names, List *parameters)
NameListToString(analyzeName));
#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
@@ -764,6 +747,12 @@ DefineDomain(CreateDomainStmt *stmt)
if (aclresult != ACLCHECK_OK)
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
*/
@@ -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 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 e04c33e4ad7..eaab97f7535 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 5b1ba143b1c..88b912977e9 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1481,6 +1481,15 @@ _equalAlterOperatorStmt(const AlterOperatorStmt *a, const AlterOperatorStmt *b)
return true;
}
+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)
{
@@ -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 96e7fdbcfe2..7e384f956c8 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
@@ -9365,6 +9366,24 @@ operator_def_arg:
| Sconst { $$ = (Node *)makeString($1); }
;
+/*****************************************************************************
+ *
+ * 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 1b460a26126..b1f7f6e2d01 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 cdf6331a971..854f133f9be 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,24 +81,31 @@ static HTAB *TypeCacheHash = NULL;
static TypeCacheEntry *firstDomainTypeEntry = NULL;
/* Private flag bits in the TypeCacheEntry.flags field */
-#define TCFLAGS_CHECKED_BTREE_OPCLASS 0x000001
-#define TCFLAGS_CHECKED_HASH_OPCLASS 0x000002
-#define TCFLAGS_CHECKED_EQ_OPR 0x000004
-#define TCFLAGS_CHECKED_LT_OPR 0x000008
-#define TCFLAGS_CHECKED_GT_OPR 0x000010
-#define TCFLAGS_CHECKED_CMP_PROC 0x000020
-#define TCFLAGS_CHECKED_HASH_PROC 0x000040
-#define TCFLAGS_CHECKED_HASH_EXTENDED_PROC 0x000080
-#define TCFLAGS_CHECKED_ELEM_PROPERTIES 0x000100
-#define TCFLAGS_HAVE_ELEM_EQUALITY 0x000200
-#define TCFLAGS_HAVE_ELEM_COMPARE 0x000400
-#define TCFLAGS_HAVE_ELEM_HASHING 0x000800
-#define TCFLAGS_HAVE_ELEM_EXTENDED_HASHING 0x001000
-#define TCFLAGS_CHECKED_FIELD_PROPERTIES 0x002000
-#define TCFLAGS_HAVE_FIELD_EQUALITY 0x004000
-#define TCFLAGS_HAVE_FIELD_COMPARE 0x008000
-#define TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS 0x010000
-#define TCFLAGS_DOMAIN_BASE_IS_COMPOSITE 0x020000
+#define TCFLAGS_HAVE_PG_TYPE_DATA 0x000001
+#define TCFLAGS_CHECKED_BTREE_OPCLASS 0x000002
+#define TCFLAGS_CHECKED_HASH_OPCLASS 0x000004
+#define TCFLAGS_CHECKED_EQ_OPR 0x000008
+#define TCFLAGS_CHECKED_LT_OPR 0x000010
+#define TCFLAGS_CHECKED_GT_OPR 0x000020
+#define TCFLAGS_CHECKED_CMP_PROC 0x000040
+#define TCFLAGS_CHECKED_HASH_PROC 0x000080
+#define TCFLAGS_CHECKED_HASH_EXTENDED_PROC 0x000100
+#define TCFLAGS_CHECKED_ELEM_PROPERTIES 0x000200
+#define TCFLAGS_HAVE_ELEM_EQUALITY 0x000400
+#define TCFLAGS_HAVE_ELEM_COMPARE 0x000800
+#define TCFLAGS_HAVE_ELEM_HASHING 0x001000
+#define TCFLAGS_HAVE_ELEM_EXTENDED_HASHING 0x002000
+#define TCFLAGS_CHECKED_FIELD_PROPERTIES 0x004000
+#define TCFLAGS_HAVE_FIELD_EQUALITY 0x008000
+#define TCFLAGS_HAVE_FIELD_COMPARE 0x010000
+#define TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS 0x020000
+#define TCFLAGS_DOMAIN_BASE_IS_COMPOSITE 0x040000
+
+/* 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
@@ -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,12 +2271,12 @@ 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
- * for table constraints/types than domain constraints, so we'll do a lot of
+ * It's slightly annoying that we can't tell whether the inval event was for
+ * a domain constraint record or not; there's usually more update traffic
+ * for table constraints than domain constraints, so we'll do a lot of
* useless flushes. Still, this is better than the old no-caching-at-all
* approach to domain constraints.
*/
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b6b08d0ccb6..54d0317500b 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 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 f972f941e05..97890946c5d 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 fc18d643471..0162bc2ffef 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 baced7eec0f..8a76afe8ccb 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 da0706add59..2039b424499 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 66ff17dbd52..cdd20e56d70 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 eb55e255d6f..86a8b65450f 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 68b04fd4feb..5b176bb2aed 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;