From fe30e7ebfa3846416f1adeb7cf611006513a4ee0 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 6 Mar 2020 12:19:29 -0500 Subject: [PATCH] Allow ALTER TYPE to change some properties of a base type. Specifically, this patch allows ALTER TYPE to: * Change the default TOAST strategy for a toastable base type; * Promote a non-toastable type to toastable; * Add/remove binary I/O functions for a type; * Add/remove typmod I/O functions for a type; * Add/remove a custom ANALYZE statistics functions for a type. The first of these can be done by the type's owner; all the others require superuser privilege since misuse could cause problems. The main motivation for this patch is to allow extensions to upgrade the feature sets of their data types, so the set of alterable properties is biased towards that use-case. However it's also true that changing some other properties would be a lot harder, as they get baked into physical storage and/or stored expressions that depend on the type. Along the way, refactor GenerateTypeDependencies() to make it easier to call, refactor DefineType's volatility checks so they can be shared by AlterType, and teach typcache.c that it might have to reload data from the type's pg_type row, a scenario it never handled before. Also rearrange alter_type.sgml a bit for clarity (put the composite-type operations together). Tomas Vondra and Tom Lane Discussion: https://postgr.es/m/20200228004440.b23ein4qvmxnlpht@development --- doc/src/sgml/catalogs.sgml | 28 +- doc/src/sgml/ref/alter_type.sgml | 179 ++++++-- src/backend/catalog/pg_type.c | 45 +- src/backend/commands/typecmds.c | 490 +++++++++++++++++++--- src/backend/nodes/copyfuncs.c | 14 + src/backend/nodes/equalfuncs.c | 12 + src/backend/parser/gram.y | 21 +- src/backend/tcop/utility.c | 13 + src/backend/utils/cache/typcache.c | 158 +++++-- src/bin/psql/tab-complete.c | 2 +- src/include/catalog/pg_type.h | 4 +- src/include/commands/typecmds.h | 2 + src/include/nodes/nodes.h | 1 + src/include/nodes/parsenodes.h | 13 +- src/include/utils/typcache.h | 2 + src/test/regress/expected/create_type.out | 78 ++++ src/test/regress/sql/create_type.sql | 57 +++ 17 files changed, 970 insertions(+), 149 deletions(-) diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 34bc0d0526..c6f95fa688 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 67be1dd568..e0afaf8d0b 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 56e0bcf39e..cd56714968 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 d732a3af45..b088ca848d 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 e04c33e4ad..eaab97f753 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 5b1ba143b1..88b912977e 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 96e7fdbcfe..7e384f956c 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 1b460a2612..b1f7f6e2d0 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 cdf6331a97..854f133f9b 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 b6b08d0ccb..54d0317500 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 f972f941e0..97890946c5 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 fc18d64347..0162bc2ffe 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 baced7eec0..8a76afe8cc 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 da0706add5..2039b42449 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 66ff17dbd5..cdd20e56d7 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 eb55e255d6..86a8b65450 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 68b04fd4fe..5b176bb2ae 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;