diff options
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/catalog/pg_type.c | 45 | ||||
-rw-r--r-- | src/backend/commands/typecmds.c | 490 | ||||
-rw-r--r-- | src/backend/nodes/copyfuncs.c | 14 | ||||
-rw-r--r-- | src/backend/nodes/equalfuncs.c | 12 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 21 | ||||
-rw-r--r-- | src/backend/tcop/utility.c | 13 | ||||
-rw-r--r-- | src/backend/utils/cache/typcache.c | 158 |
7 files changed, 652 insertions, 101 deletions
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c index 56e0bcf39ee..cd567149689 100644 --- a/src/backend/catalog/pg_type.c +++ b/src/backend/catalog/pg_type.c @@ -155,8 +155,8 @@ TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId) * Create dependencies. We can/must skip this in bootstrap mode. */ if (!IsBootstrapProcessingMode()) - GenerateTypeDependencies(typoid, - (Form_pg_type) GETSTRUCT(tup), + GenerateTypeDependencies(tup, + pg_type_desc, NULL, NULL, 0, @@ -488,8 +488,8 @@ TypeCreate(Oid newTypeOid, * Create dependencies. We can/must skip this in bootstrap mode. */ if (!IsBootstrapProcessingMode()) - GenerateTypeDependencies(typeObjectId, - (Form_pg_type) GETSTRUCT(tup), + GenerateTypeDependencies(tup, + pg_type_desc, (defaultTypeBin ? stringToNode(defaultTypeBin) : NULL), @@ -516,12 +516,16 @@ TypeCreate(Oid newTypeOid, * GenerateTypeDependencies: build the dependencies needed for a type * * Most of what this function needs to know about the type is passed as the - * new pg_type row, typeForm. But we can't get at the varlena fields through - * that, so defaultExpr and typacl are passed separately. (typacl is really + * new pg_type row, typeTuple. We make callers pass the pg_type Relation + * as well, so that we have easy access to a tuple descriptor for the row. + * + * While this is able to extract the defaultExpr and typacl from the tuple, + * doing so is relatively expensive, and callers may have those values at + * hand already. Pass those if handy, otherwise pass NULL. (typacl is really * "Acl *", but we declare it "void *" to avoid including acl.h in pg_type.h.) * - * relationKind and isImplicitArray aren't visible in the pg_type row either, - * so they're also passed separately. + * relationKind and isImplicitArray are likewise somewhat expensive to deduce + * from the tuple, so we make callers pass those (they're not optional). * * isDependentType is true if this is an implicit array or relation rowtype; * that means it doesn't need its own dependencies on owner etc. @@ -535,8 +539,8 @@ TypeCreate(Oid newTypeOid, * that type will become a member of the extension.) */ void -GenerateTypeDependencies(Oid typeObjectId, - Form_pg_type typeForm, +GenerateTypeDependencies(HeapTuple typeTuple, + Relation typeCatalog, Node *defaultExpr, void *typacl, char relationKind, /* only for relation rowtypes */ @@ -544,9 +548,30 @@ GenerateTypeDependencies(Oid typeObjectId, bool isDependentType, bool rebuild) { + Form_pg_type typeForm = (Form_pg_type) GETSTRUCT(typeTuple); + Oid typeObjectId = typeForm->oid; + Datum datum; + bool isNull; ObjectAddress myself, referenced; + /* Extract defaultExpr if caller didn't pass it */ + if (defaultExpr == NULL) + { + datum = heap_getattr(typeTuple, Anum_pg_type_typdefaultbin, + RelationGetDescr(typeCatalog), &isNull); + if (!isNull) + defaultExpr = stringToNode(TextDatumGetCString(datum)); + } + /* Extract typacl if caller didn't pass it */ + if (typacl == NULL) + { + datum = heap_getattr(typeTuple, Anum_pg_type_typacl, + RelationGetDescr(typeCatalog), &isNull); + if (!isNull) + typacl = DatumGetAclPCopy(datum); + } + /* If rebuild, first flush old dependencies, except extension deps */ if (rebuild) { diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index d732a3af450..b088ca848d3 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -83,6 +83,25 @@ typedef struct /* atts[] is of allocated length RelationGetNumberOfAttributes(rel) */ } RelToCheck; +/* parameter structure for AlterTypeRecurse() */ +typedef struct +{ + /* Flags indicating which type attributes to update */ + bool updateStorage; + bool updateReceive; + bool updateSend; + bool updateTypmodin; + bool updateTypmodout; + bool updateAnalyze; + /* New values for relevant attributes */ + char storage; + Oid receiveOid; + Oid sendOid; + Oid typmodinOid; + Oid typmodoutOid; + Oid analyzeOid; +} AlterTypeRecurseParams; + /* Potentially set by pg_upgrade_support functions */ Oid binary_upgrade_next_array_pg_type_oid = InvalidOid; @@ -107,6 +126,8 @@ static char *domainAddConstraint(Oid domainOid, Oid domainNamespace, const char *domainName, ObjectAddress *constrAddr); static Node *replace_domain_constraint_value(ParseState *pstate, ColumnRef *cref); +static void AlterTypeRecurse(Oid typeOid, HeapTuple tup, Relation catalog, + AlterTypeRecurseParams *atparams); /* @@ -466,9 +487,12 @@ DefineType(ParseState *pstate, List *names, List *parameters) * minimum sane check would be for execute-with-grant-option. But we * don't have a way to make the type go away if the grant option is * revoked, so ownership seems better. + * + * XXX For now, this is all unnecessary given the superuser check above. + * If we ever relax that, these calls likely should be moved into + * findTypeInputFunction et al, where they could be shared by AlterType. */ #ifdef NOT_USED - /* XXX this is unnecessary given the superuser check above */ if (inputOid && !pg_proc_ownercheck(inputOid, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION, NameListToString(inputName)); @@ -493,47 +517,6 @@ DefineType(ParseState *pstate, List *names, List *parameters) #endif /* - * Print warnings if any of the type's I/O functions are marked volatile. - * There is a general assumption that I/O functions are stable or - * immutable; this allows us for example to mark record_in/record_out - * stable rather than volatile. Ideally we would throw errors not just - * warnings here; but since this check is new as of 9.5, and since the - * volatility marking might be just an error-of-omission and not a true - * indication of how the function behaves, we'll let it pass as a warning - * for now. - */ - if (inputOid && func_volatile(inputOid) == PROVOLATILE_VOLATILE) - ereport(WARNING, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("type input function %s should not be volatile", - NameListToString(inputName)))); - if (outputOid && func_volatile(outputOid) == PROVOLATILE_VOLATILE) - ereport(WARNING, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("type output function %s should not be volatile", - NameListToString(outputName)))); - if (receiveOid && func_volatile(receiveOid) == PROVOLATILE_VOLATILE) - ereport(WARNING, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("type receive function %s should not be volatile", - NameListToString(receiveName)))); - if (sendOid && func_volatile(sendOid) == PROVOLATILE_VOLATILE) - ereport(WARNING, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("type send function %s should not be volatile", - NameListToString(sendName)))); - if (typmodinOid && func_volatile(typmodinOid) == PROVOLATILE_VOLATILE) - ereport(WARNING, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("type modifier input function %s should not be volatile", - NameListToString(typmodinName)))); - if (typmodoutOid && func_volatile(typmodoutOid) == PROVOLATILE_VOLATILE) - ereport(WARNING, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("type modifier output function %s should not be volatile", - NameListToString(typmodoutName)))); - - /* * OK, we're done checking, time to make the type. We must assign the * array type OID ahead of calling TypeCreate, since the base type and * array type each refer to the other. @@ -765,6 +748,12 @@ DefineDomain(CreateDomainStmt *stmt) aclcheck_error_type(aclresult, basetypeoid); /* + * Collect the properties of the new domain. Some are inherited from the + * base type, some are not. If you change any of this inheritance + * behavior, be sure to update AlterTypeRecurse() to match! + */ + + /* * Identify the collation if any */ baseColl = baseType->typcollation; @@ -1664,6 +1653,22 @@ findTypeInputFunction(List *procname, Oid typeOid) errmsg("type input function %s must return type %s", NameListToString(procname), format_type_be(typeOid)))); + /* + * Print warnings if any of the type's I/O functions are marked volatile. + * There is a general assumption that I/O functions are stable or + * immutable; this allows us for example to mark record_in/record_out + * stable rather than volatile. Ideally we would throw errors not just + * warnings here; but since this check is new as of 9.5, and since the + * volatility marking might be just an error-of-omission and not a true + * indication of how the function behaves, we'll let it pass as a warning + * for now. + */ + if (func_volatile(procOid) == PROVOLATILE_VOLATILE) + ereport(WARNING, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("type input function %s should not be volatile", + NameListToString(procname)))); + return procOid; } @@ -1692,6 +1697,13 @@ findTypeOutputFunction(List *procname, Oid typeOid) errmsg("type output function %s must return type %s", NameListToString(procname), "cstring"))); + /* Just a warning for now, per comments in findTypeInputFunction */ + if (func_volatile(procOid) == PROVOLATILE_VOLATILE) + ereport(WARNING, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("type output function %s should not be volatile", + NameListToString(procname)))); + return procOid; } @@ -1728,6 +1740,13 @@ findTypeReceiveFunction(List *procname, Oid typeOid) errmsg("type receive function %s must return type %s", NameListToString(procname), format_type_be(typeOid)))); + /* Just a warning for now, per comments in findTypeInputFunction */ + if (func_volatile(procOid) == PROVOLATILE_VOLATILE) + ereport(WARNING, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("type receive function %s should not be volatile", + NameListToString(procname)))); + return procOid; } @@ -1756,6 +1775,13 @@ findTypeSendFunction(List *procname, Oid typeOid) errmsg("type send function %s must return type %s", NameListToString(procname), "bytea"))); + /* Just a warning for now, per comments in findTypeInputFunction */ + if (func_volatile(procOid) == PROVOLATILE_VOLATILE) + ereport(WARNING, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("type send function %s should not be volatile", + NameListToString(procname)))); + return procOid; } @@ -1783,6 +1809,13 @@ findTypeTypmodinFunction(List *procname) errmsg("typmod_in function %s must return type %s", NameListToString(procname), "integer"))); + /* Just a warning for now, per comments in findTypeInputFunction */ + if (func_volatile(procOid) == PROVOLATILE_VOLATILE) + ereport(WARNING, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("type modifier input function %s should not be volatile", + NameListToString(procname)))); + return procOid; } @@ -1810,6 +1843,13 @@ findTypeTypmodoutFunction(List *procname) errmsg("typmod_out function %s must return type %s", NameListToString(procname), "cstring"))); + /* Just a warning for now, per comments in findTypeInputFunction */ + if (func_volatile(procOid) == PROVOLATILE_VOLATILE) + ereport(WARNING, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("type modifier output function %s should not be volatile", + NameListToString(procname)))); + return procOid; } @@ -2086,9 +2126,6 @@ AlterDomainDefault(List *names, Node *defaultRaw) Relation rel; char *defaultValue; Node *defaultExpr = NULL; /* NULL if no default specified */ - Acl *typacl; - Datum aclDatum; - bool isNull; Datum new_record[Natts_pg_type]; bool new_record_nulls[Natts_pg_type]; bool new_record_repl[Natts_pg_type]; @@ -2141,6 +2178,7 @@ AlterDomainDefault(List *names, Node *defaultRaw) (IsA(defaultExpr, Const) &&((Const *) defaultExpr)->constisnull)) { /* Default is NULL, drop it */ + defaultExpr = NULL; new_record_nulls[Anum_pg_type_typdefaultbin - 1] = true; new_record_repl[Anum_pg_type_typdefaultbin - 1] = true; new_record_nulls[Anum_pg_type_typdefault - 1] = true; @@ -2181,19 +2219,11 @@ AlterDomainDefault(List *names, Node *defaultRaw) CatalogTupleUpdate(rel, &tup->t_self, newtuple); - /* Must extract ACL for use of GenerateTypeDependencies */ - aclDatum = heap_getattr(newtuple, Anum_pg_type_typacl, - RelationGetDescr(rel), &isNull); - if (isNull) - typacl = NULL; - else - typacl = DatumGetAclPCopy(aclDatum); - /* Rebuild dependencies */ - GenerateTypeDependencies(domainoid, - (Form_pg_type) GETSTRUCT(newtuple), + GenerateTypeDependencies(newtuple, + rel, defaultExpr, - typacl, + NULL, /* don't have typacl handy */ 0, /* relation kind is n/a */ false, /* a domain isn't an implicit array */ false, /* nor is it any kind of dependent type */ @@ -3609,3 +3639,351 @@ AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid, return oldNspOid; } + +/* + * AlterType + * ALTER TYPE <type> SET (option = ...) + * + * NOTE: the set of changes that can be allowed here is constrained by many + * non-obvious implementation restrictions. Tread carefully when considering + * adding new flexibility. + */ +ObjectAddress +AlterType(AlterTypeStmt *stmt) +{ + ObjectAddress address; + Relation catalog; + TypeName *typename; + HeapTuple tup; + Oid typeOid; + Form_pg_type typForm; + bool requireSuper = false; + AlterTypeRecurseParams atparams; + ListCell *pl; + + catalog = table_open(TypeRelationId, RowExclusiveLock); + + /* Make a TypeName so we can use standard type lookup machinery */ + typename = makeTypeNameFromNameList(stmt->typeName); + tup = typenameType(NULL, typename, NULL); + + typeOid = typeTypeId(tup); + typForm = (Form_pg_type) GETSTRUCT(tup); + + /* Process options */ + memset(&atparams, 0, sizeof(atparams)); + foreach(pl, stmt->options) + { + DefElem *defel = (DefElem *) lfirst(pl); + + if (strcmp(defel->defname, "storage") == 0) + { + char *a = defGetString(defel); + + if (pg_strcasecmp(a, "plain") == 0) + atparams.storage = TYPSTORAGE_PLAIN; + else if (pg_strcasecmp(a, "external") == 0) + atparams.storage = TYPSTORAGE_EXTERNAL; + else if (pg_strcasecmp(a, "extended") == 0) + atparams.storage = TYPSTORAGE_EXTENDED; + else if (pg_strcasecmp(a, "main") == 0) + atparams.storage = TYPSTORAGE_MAIN; + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("storage \"%s\" not recognized", a))); + + /* + * Validate the storage request. If the type isn't varlena, it + * certainly doesn't support non-PLAIN storage. + */ + if (atparams.storage != TYPSTORAGE_PLAIN && typForm->typlen != -1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("fixed-size types must have storage PLAIN"))); + + /* + * Switching from PLAIN to non-PLAIN is allowed, but it requires + * superuser, since we can't validate that the type's C functions + * will support it. Switching from non-PLAIN to PLAIN is + * disallowed outright, because it's not practical to ensure that + * no tables have toasted values of the type. Switching among + * different non-PLAIN settings is OK, since it just constitutes a + * change in the strategy requested for columns created in the + * future. + */ + if (atparams.storage != TYPSTORAGE_PLAIN && + typForm->typstorage == TYPSTORAGE_PLAIN) + requireSuper = true; + else if (atparams.storage == TYPSTORAGE_PLAIN && + typForm->typstorage != TYPSTORAGE_PLAIN) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("cannot change type's storage to PLAIN"))); + + atparams.updateStorage = true; + } + else if (strcmp(defel->defname, "receive") == 0) + { + if (defel->arg != NULL) + atparams.receiveOid = + findTypeReceiveFunction(defGetQualifiedName(defel), + typeOid); + else + atparams.receiveOid = InvalidOid; /* NONE, remove function */ + atparams.updateReceive = true; + /* Replacing an I/O function requires superuser. */ + requireSuper = true; + } + else if (strcmp(defel->defname, "send") == 0) + { + if (defel->arg != NULL) + atparams.sendOid = + findTypeSendFunction(defGetQualifiedName(defel), + typeOid); + else + atparams.sendOid = InvalidOid; /* NONE, remove function */ + atparams.updateSend = true; + /* Replacing an I/O function requires superuser. */ + requireSuper = true; + } + else if (strcmp(defel->defname, "typmod_in") == 0) + { + if (defel->arg != NULL) + atparams.typmodinOid = + findTypeTypmodinFunction(defGetQualifiedName(defel)); + else + atparams.typmodinOid = InvalidOid; /* NONE, remove function */ + atparams.updateTypmodin = true; + /* Replacing an I/O function requires superuser. */ + requireSuper = true; + } + else if (strcmp(defel->defname, "typmod_out") == 0) + { + if (defel->arg != NULL) + atparams.typmodoutOid = + findTypeTypmodoutFunction(defGetQualifiedName(defel)); + else + atparams.typmodoutOid = InvalidOid; /* NONE, remove function */ + atparams.updateTypmodout = true; + /* Replacing an I/O function requires superuser. */ + requireSuper = true; + } + else if (strcmp(defel->defname, "analyze") == 0) + { + if (defel->arg != NULL) + atparams.analyzeOid = + findTypeAnalyzeFunction(defGetQualifiedName(defel), + typeOid); + else + atparams.analyzeOid = InvalidOid; /* NONE, remove function */ + atparams.updateAnalyze = true; + /* Replacing an analyze function requires superuser. */ + requireSuper = true; + } + + /* + * The rest of the options that CREATE accepts cannot be changed. + * Check for them so that we can give a meaningful error message. + */ + else if (strcmp(defel->defname, "input") == 0 || + strcmp(defel->defname, "output") == 0 || + strcmp(defel->defname, "internallength") == 0 || + strcmp(defel->defname, "passedbyvalue") == 0 || + strcmp(defel->defname, "alignment") == 0 || + strcmp(defel->defname, "like") == 0 || + strcmp(defel->defname, "category") == 0 || + strcmp(defel->defname, "preferred") == 0 || + strcmp(defel->defname, "default") == 0 || + strcmp(defel->defname, "element") == 0 || + strcmp(defel->defname, "delimiter") == 0 || + strcmp(defel->defname, "collatable") == 0) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("type attribute \"%s\" cannot be changed", + defel->defname))); + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("type attribute \"%s\" not recognized", + defel->defname))); + } + + /* + * Permissions check. Require superuser if we decided the command + * requires that, else must own the type. + */ + if (requireSuper) + { + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to alter a type"))); + } + else + { + if (!pg_type_ownercheck(typeOid, GetUserId())) + aclcheck_error_type(ACLCHECK_NOT_OWNER, typeOid); + } + + /* + * We disallow all forms of ALTER TYPE SET on types that aren't plain base + * types. It would for example be highly unsafe, not to mention + * pointless, to change the send/receive functions for a composite type. + * Moreover, pg_dump has no support for changing these properties on + * non-base types. We might weaken this someday, but not now. + * + * Note: if you weaken this enough to allow composite types, be sure to + * adjust the GenerateTypeDependencies call in AlterTypeRecurse. + */ + if (typForm->typtype != TYPTYPE_BASE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("%s is not a base type", + format_type_be(typeOid)))); + + /* + * For the same reasons, don't allow direct alteration of array types. + */ + if (OidIsValid(typForm->typelem) && + get_array_type(typForm->typelem) == typeOid) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("%s is not a base type", + format_type_be(typeOid)))); + + /* OK, recursively update this type and any domains over it */ + AlterTypeRecurse(typeOid, tup, catalog, &atparams); + + /* Clean up */ + ReleaseSysCache(tup); + + table_close(catalog, RowExclusiveLock); + + ObjectAddressSet(address, TypeRelationId, typeOid); + + return address; +} + +/* + * AlterTypeRecurse: one recursion step for AlterType() + * + * Apply the changes specified by "atparams" to the type identified by + * "typeOid", whose existing pg_type tuple is "tup". Then search for any + * domains over this type, and recursively apply (most of) the same changes + * to those domains. + * + * We need this because the system generally assumes that a domain inherits + * many properties from its base type. See DefineDomain() above for details + * of what is inherited. + * + * There's a race condition here, in that some other transaction could + * concurrently add another domain atop this base type; we'd miss updating + * that one. Hence, be wary of allowing ALTER TYPE to change properties for + * which it'd be really fatal for a domain to be out of sync with its base + * type (typlen, for example). In practice, races seem unlikely to be an + * issue for plausible use-cases for ALTER TYPE. If one does happen, it could + * be fixed by re-doing the same ALTER TYPE once all prior transactions have + * committed. + */ +static void +AlterTypeRecurse(Oid typeOid, HeapTuple tup, Relation catalog, + AlterTypeRecurseParams *atparams) +{ + Datum values[Natts_pg_type]; + bool nulls[Natts_pg_type]; + bool replaces[Natts_pg_type]; + HeapTuple newtup; + SysScanDesc scan; + ScanKeyData key[1]; + HeapTuple domainTup; + + /* Since this function recurses, it could be driven to stack overflow */ + check_stack_depth(); + + /* Update the current type's tuple */ + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + memset(replaces, 0, sizeof(replaces)); + + if (atparams->updateStorage) + { + replaces[Anum_pg_type_typstorage - 1] = true; + values[Anum_pg_type_typstorage - 1] = CharGetDatum(atparams->storage); + } + if (atparams->updateReceive) + { + replaces[Anum_pg_type_typreceive - 1] = true; + values[Anum_pg_type_typreceive - 1] = ObjectIdGetDatum(atparams->receiveOid); + } + if (atparams->updateSend) + { + replaces[Anum_pg_type_typsend - 1] = true; + values[Anum_pg_type_typsend - 1] = ObjectIdGetDatum(atparams->sendOid); + } + if (atparams->updateTypmodin) + { + replaces[Anum_pg_type_typmodin - 1] = true; + values[Anum_pg_type_typmodin - 1] = ObjectIdGetDatum(atparams->typmodinOid); + } + if (atparams->updateTypmodout) + { + replaces[Anum_pg_type_typmodout - 1] = true; + values[Anum_pg_type_typmodout - 1] = ObjectIdGetDatum(atparams->typmodoutOid); + } + if (atparams->updateAnalyze) + { + replaces[Anum_pg_type_typanalyze - 1] = true; + values[Anum_pg_type_typanalyze - 1] = ObjectIdGetDatum(atparams->analyzeOid); + } + + newtup = heap_modify_tuple(tup, RelationGetDescr(catalog), + values, nulls, replaces); + + CatalogTupleUpdate(catalog, &newtup->t_self, newtup); + + /* Rebuild dependencies for this type */ + GenerateTypeDependencies(newtup, + catalog, + NULL, /* don't have defaultExpr handy */ + NULL, /* don't have typacl handy */ + 0, /* we rejected composite types above */ + false, /* and we rejected implicit arrays above */ + false, /* so it can't be a dependent type */ + true); + + InvokeObjectPostAlterHook(TypeRelationId, typeOid, 0); + + /* + * Now we need to recurse to domains. However, some properties are not + * inherited by domains, so clear the update flags for those. + */ + atparams->updateReceive = false; /* domains use F_DOMAIN_RECV */ + atparams->updateTypmodin = false; /* domains don't have typmods */ + atparams->updateTypmodout = false; + + /* Search pg_type for possible domains over this type */ + ScanKeyInit(&key[0], + Anum_pg_type_typbasetype, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(typeOid)); + + scan = systable_beginscan(catalog, InvalidOid, false, + NULL, 1, key); + + while ((domainTup = systable_getnext(scan)) != NULL) + { + Form_pg_type domainForm = (Form_pg_type) GETSTRUCT(domainTup); + + /* + * Shouldn't have a nonzero typbasetype in a non-domain, but let's + * check + */ + if (domainForm->typtype != TYPTYPE_DOMAIN) + continue; + + AlterTypeRecurse(domainForm->oid, domainTup, catalog, atparams); + } + + systable_endscan(scan); +} diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index e04c33e4ad7..eaab97f7535 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3636,6 +3636,17 @@ _copyAlterOperatorStmt(const AlterOperatorStmt *from) return newnode; } +static AlterTypeStmt * +_copyAlterTypeStmt(const AlterTypeStmt *from) +{ + AlterTypeStmt *newnode = makeNode(AlterTypeStmt); + + COPY_NODE_FIELD(typeName); + COPY_NODE_FIELD(options); + + return newnode; +} + static RuleStmt * _copyRuleStmt(const RuleStmt *from) { @@ -5263,6 +5274,9 @@ copyObjectImpl(const void *from) case T_AlterOperatorStmt: retval = _copyAlterOperatorStmt(from); break; + case T_AlterTypeStmt: + retval = _copyAlterTypeStmt(from); + break; case T_RuleStmt: retval = _copyRuleStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 5b1ba143b1c..88b912977e9 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1482,6 +1482,15 @@ _equalAlterOperatorStmt(const AlterOperatorStmt *a, const AlterOperatorStmt *b) } static bool +_equalAlterTypeStmt(const AlterTypeStmt *a, const AlterTypeStmt *b) +{ + COMPARE_NODE_FIELD(typeName); + COMPARE_NODE_FIELD(options); + + return true; +} + +static bool _equalRuleStmt(const RuleStmt *a, const RuleStmt *b) { COMPARE_NODE_FIELD(relation); @@ -3359,6 +3368,9 @@ equal(const void *a, const void *b) case T_AlterOperatorStmt: retval = _equalAlterOperatorStmt(a, b); break; + case T_AlterTypeStmt: + retval = _equalAlterTypeStmt(a, b); + break; case T_RuleStmt: retval = _equalRuleStmt(a, b); break; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 96e7fdbcfe2..7e384f956c8 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -249,7 +249,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt AlterFdwStmt AlterForeignServerStmt AlterGroupStmt AlterObjectDependsStmt AlterObjectSchemaStmt AlterOwnerStmt - AlterOperatorStmt AlterSeqStmt AlterSystemStmt AlterTableStmt + AlterOperatorStmt AlterTypeStmt AlterSeqStmt AlterSystemStmt AlterTableStmt AlterTblSpcStmt AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt AlterCompositeTypeStmt AlterUserMappingStmt AlterRoleStmt AlterRoleSetStmt AlterPolicyStmt AlterStatsStmt @@ -847,6 +847,7 @@ stmt : | AlterObjectSchemaStmt | AlterOwnerStmt | AlterOperatorStmt + | AlterTypeStmt | AlterPolicyStmt | AlterSeqStmt | AlterSystemStmt @@ -9367,6 +9368,24 @@ operator_def_arg: /***************************************************************************** * + * ALTER TYPE name SET define + * + * We repurpose ALTER OPERATOR's version of "definition" here + * + *****************************************************************************/ + +AlterTypeStmt: + ALTER TYPE_P any_name SET '(' operator_def_list ')' + { + AlterTypeStmt *n = makeNode(AlterTypeStmt); + n->typeName = $3; + n->options = $6; + $$ = (Node *)n; + } + ; + +/***************************************************************************** + * * ALTER THING name OWNER TO newname * *****************************************************************************/ diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 1b460a26126..b1f7f6e2d01 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -162,6 +162,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree) case T_AlterTableMoveAllStmt: case T_AlterTableSpaceOptionsStmt: case T_AlterTableStmt: + case T_AlterTypeStmt: case T_AlterUserMappingStmt: case T_CommentStmt: case T_CompositeTypeStmt: @@ -1713,6 +1714,10 @@ ProcessUtilitySlow(ParseState *pstate, address = AlterOperator((AlterOperatorStmt *) parsetree); break; + case T_AlterTypeStmt: + address = AlterType((AlterTypeStmt *) parsetree); + break; + case T_CommentStmt: address = CommentObject((CommentStmt *) parsetree); break; @@ -2895,6 +2900,10 @@ CreateCommandTag(Node *parsetree) tag = CMDTAG_ALTER_OPERATOR; break; + case T_AlterTypeStmt: + tag = CMDTAG_ALTER_TYPE; + break; + case T_AlterTSDictionaryStmt: tag = CMDTAG_ALTER_TEXT_SEARCH_DICTIONARY; break; @@ -3251,6 +3260,10 @@ GetCommandLogLevel(Node *parsetree) lev = LOGSTMT_DDL; break; + case T_AlterTypeStmt: + lev = LOGSTMT_DDL; + break; + case T_AlterTableMoveAllStmt: case T_AlterTableStmt: lev = LOGSTMT_DDL; diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c index cdf6331a971..854f133f9be 100644 --- a/src/backend/utils/cache/typcache.c +++ b/src/backend/utils/cache/typcache.c @@ -23,11 +23,12 @@ * permanently allows caching pointers to them in long-lived places. * * We have some provisions for updating cache entries if the stored data - * becomes obsolete. Information dependent on opclasses is cleared if we - * detect updates to pg_opclass. We also support clearing the tuple - * descriptor and operator/function parts of a rowtype's cache entry, - * since those may need to change as a consequence of ALTER TABLE. - * Domain constraint changes are also tracked properly. + * becomes obsolete. Core data extracted from the pg_type row is updated + * when we detect updates to pg_type. Information dependent on opclasses is + * cleared if we detect updates to pg_opclass. We also support clearing the + * tuple descriptor and operator/function parts of a rowtype's cache entry, + * since those may need to change as a consequence of ALTER TABLE. Domain + * constraint changes are also tracked properly. * * * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group @@ -80,24 +81,31 @@ static HTAB *TypeCacheHash = NULL; static TypeCacheEntry *firstDomainTypeEntry = NULL; /* Private flag bits in the TypeCacheEntry.flags field */ -#define TCFLAGS_CHECKED_BTREE_OPCLASS 0x000001 -#define TCFLAGS_CHECKED_HASH_OPCLASS 0x000002 -#define TCFLAGS_CHECKED_EQ_OPR 0x000004 -#define TCFLAGS_CHECKED_LT_OPR 0x000008 -#define TCFLAGS_CHECKED_GT_OPR 0x000010 -#define TCFLAGS_CHECKED_CMP_PROC 0x000020 -#define TCFLAGS_CHECKED_HASH_PROC 0x000040 -#define TCFLAGS_CHECKED_HASH_EXTENDED_PROC 0x000080 -#define TCFLAGS_CHECKED_ELEM_PROPERTIES 0x000100 -#define TCFLAGS_HAVE_ELEM_EQUALITY 0x000200 -#define TCFLAGS_HAVE_ELEM_COMPARE 0x000400 -#define TCFLAGS_HAVE_ELEM_HASHING 0x000800 -#define TCFLAGS_HAVE_ELEM_EXTENDED_HASHING 0x001000 -#define TCFLAGS_CHECKED_FIELD_PROPERTIES 0x002000 -#define TCFLAGS_HAVE_FIELD_EQUALITY 0x004000 -#define TCFLAGS_HAVE_FIELD_COMPARE 0x008000 -#define TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS 0x010000 -#define TCFLAGS_DOMAIN_BASE_IS_COMPOSITE 0x020000 +#define TCFLAGS_HAVE_PG_TYPE_DATA 0x000001 +#define TCFLAGS_CHECKED_BTREE_OPCLASS 0x000002 +#define TCFLAGS_CHECKED_HASH_OPCLASS 0x000004 +#define TCFLAGS_CHECKED_EQ_OPR 0x000008 +#define TCFLAGS_CHECKED_LT_OPR 0x000010 +#define TCFLAGS_CHECKED_GT_OPR 0x000020 +#define TCFLAGS_CHECKED_CMP_PROC 0x000040 +#define TCFLAGS_CHECKED_HASH_PROC 0x000080 +#define TCFLAGS_CHECKED_HASH_EXTENDED_PROC 0x000100 +#define TCFLAGS_CHECKED_ELEM_PROPERTIES 0x000200 +#define TCFLAGS_HAVE_ELEM_EQUALITY 0x000400 +#define TCFLAGS_HAVE_ELEM_COMPARE 0x000800 +#define TCFLAGS_HAVE_ELEM_HASHING 0x001000 +#define TCFLAGS_HAVE_ELEM_EXTENDED_HASHING 0x002000 +#define TCFLAGS_CHECKED_FIELD_PROPERTIES 0x004000 +#define TCFLAGS_HAVE_FIELD_EQUALITY 0x008000 +#define TCFLAGS_HAVE_FIELD_COMPARE 0x010000 +#define TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS 0x020000 +#define TCFLAGS_DOMAIN_BASE_IS_COMPOSITE 0x040000 + +/* The flags associated with equality/comparison/hashing are all but these: */ +#define TCFLAGS_OPERATOR_FLAGS \ + (~(TCFLAGS_HAVE_PG_TYPE_DATA | \ + TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS | \ + TCFLAGS_DOMAIN_BASE_IS_COMPOSITE)) /* * Data stored about a domain type's constraints. Note that we do not create @@ -295,6 +303,7 @@ static bool range_element_has_hashing(TypeCacheEntry *typentry); static bool range_element_has_extended_hashing(TypeCacheEntry *typentry); static void cache_range_element_properties(TypeCacheEntry *typentry); static void TypeCacheRelCallback(Datum arg, Oid relid); +static void TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue); static void TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue); static void TypeCacheConstrCallback(Datum arg, int cacheid, uint32 hashvalue); static void load_enum_cache_data(TypeCacheEntry *tcache); @@ -337,9 +346,9 @@ lookup_type_cache(Oid type_id, int flags) /* Also set up callbacks for SI invalidations */ CacheRegisterRelcacheCallback(TypeCacheRelCallback, (Datum) 0); + CacheRegisterSyscacheCallback(TYPEOID, TypeCacheTypCallback, (Datum) 0); CacheRegisterSyscacheCallback(CLAOID, TypeCacheOpcCallback, (Datum) 0); CacheRegisterSyscacheCallback(CONSTROID, TypeCacheConstrCallback, (Datum) 0); - CacheRegisterSyscacheCallback(TYPEOID, TypeCacheConstrCallback, (Datum) 0); /* Also make sure CacheMemoryContext exists */ if (!CacheMemoryContext) @@ -381,7 +390,13 @@ lookup_type_cache(Oid type_id, int flags) Assert(!found); /* it wasn't there a moment ago */ MemSet(typentry, 0, sizeof(TypeCacheEntry)); + + /* These fields can never change, by definition */ typentry->type_id = type_id; + typentry->type_id_hash = GetSysCacheHashValue1(TYPEOID, + ObjectIdGetDatum(type_id)); + + /* Keep this part in sync with the code below */ typentry->typlen = typtup->typlen; typentry->typbyval = typtup->typbyval; typentry->typalign = typtup->typalign; @@ -390,6 +405,7 @@ lookup_type_cache(Oid type_id, int flags) typentry->typrelid = typtup->typrelid; typentry->typelem = typtup->typelem; typentry->typcollation = typtup->typcollation; + typentry->flags |= TCFLAGS_HAVE_PG_TYPE_DATA; /* If it's a domain, immediately thread it into the domain cache list */ if (typentry->typtype == TYPTYPE_DOMAIN) @@ -400,6 +416,43 @@ lookup_type_cache(Oid type_id, int flags) ReleaseSysCache(tp); } + else if (!(typentry->flags & TCFLAGS_HAVE_PG_TYPE_DATA)) + { + /* + * We have an entry, but its pg_type row got changed, so reload the + * data obtained directly from pg_type. + */ + HeapTuple tp; + Form_pg_type typtup; + + tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_id)); + if (!HeapTupleIsValid(tp)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("type with OID %u does not exist", type_id))); + typtup = (Form_pg_type) GETSTRUCT(tp); + if (!typtup->typisdefined) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("type \"%s\" is only a shell", + NameStr(typtup->typname)))); + + /* + * Keep this part in sync with the code above. Many of these fields + * shouldn't ever change, particularly typtype, but copy 'em anyway. + */ + typentry->typlen = typtup->typlen; + typentry->typbyval = typtup->typbyval; + typentry->typalign = typtup->typalign; + typentry->typstorage = typtup->typstorage; + typentry->typtype = typtup->typtype; + typentry->typrelid = typtup->typrelid; + typentry->typelem = typtup->typelem; + typentry->typcollation = typtup->typcollation; + typentry->flags |= TCFLAGS_HAVE_PG_TYPE_DATA; + + ReleaseSysCache(tp); + } /* * Look up opclasses if we haven't already and any dependent info is @@ -750,12 +803,17 @@ lookup_type_cache(Oid type_id, int flags) /* * If requested, get information about a range type + * + * This includes making sure that the basic info about the range element + * type is up-to-date. */ if ((flags & TYPECACHE_RANGE_INFO) && - typentry->rngelemtype == NULL && typentry->typtype == TYPTYPE_RANGE) { - load_rangetype_info(typentry); + if (typentry->rngelemtype == NULL) + load_rangetype_info(typentry); + else if (!(typentry->rngelemtype->flags & TCFLAGS_HAVE_PG_TYPE_DATA)) + (void) lookup_type_cache(typentry->rngelemtype->type_id, 0); } /* @@ -2129,7 +2187,7 @@ TypeCacheRelCallback(Datum arg, Oid relid) } /* Reset equality/comparison/hashing validity information */ - typentry->flags = 0; + typentry->flags &= ~TCFLAGS_OPERATOR_FLAGS; } else if (typentry->typtype == TYPTYPE_DOMAIN) { @@ -2140,7 +2198,39 @@ TypeCacheRelCallback(Datum arg, Oid relid) * type is composite, we don't need to reset anything. */ if (typentry->flags & TCFLAGS_DOMAIN_BASE_IS_COMPOSITE) - typentry->flags = 0; + typentry->flags &= ~TCFLAGS_OPERATOR_FLAGS; + } + } +} + +/* + * TypeCacheTypCallback + * Syscache inval callback function + * + * This is called when a syscache invalidation event occurs for any + * pg_type row. If we have information cached about that type, mark + * it as needing to be reloaded. + */ +static void +TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue) +{ + HASH_SEQ_STATUS status; + TypeCacheEntry *typentry; + + /* TypeCacheHash must exist, else this callback wouldn't be registered */ + hash_seq_init(&status, TypeCacheHash); + while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL) + { + /* Is this the targeted type row (or it's a total cache flush)? */ + if (hashvalue == 0 || typentry->type_id_hash == hashvalue) + { + /* + * Mark the data obtained directly from pg_type as invalid. Also, + * if it's a domain, typnotnull might've changed, so we'll need to + * recalculate its constraints. + */ + typentry->flags &= ~(TCFLAGS_HAVE_PG_TYPE_DATA | + TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS); } } } @@ -2172,7 +2262,7 @@ TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue) while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL) { /* Reset equality/comparison/hashing validity information */ - typentry->flags = 0; + typentry->flags &= ~TCFLAGS_OPERATOR_FLAGS; } } @@ -2181,12 +2271,12 @@ TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue) * Syscache inval callback function * * This is called when a syscache invalidation event occurs for any - * pg_constraint or pg_type row. We flush information about domain - * constraints when this happens. + * pg_constraint row. We flush information about domain constraints + * when this happens. * - * It's slightly annoying that we can't tell whether the inval event was for a - * domain constraint/type record or not; there's usually more update traffic - * for table constraints/types than domain constraints, so we'll do a lot of + * It's slightly annoying that we can't tell whether the inval event was for + * a domain constraint record or not; there's usually more update traffic + * for table constraints than domain constraints, so we'll do a lot of * useless flushes. Still, this is better than the old no-caching-at-all * approach to domain constraints. */ |