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;