1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-07 00:36:50 +03:00

Allow ALTER TYPE to change some properties of a base type.

Specifically, this patch allows ALTER TYPE to:
* Change the default TOAST strategy for a toastable base type;
* Promote a non-toastable type to toastable;
* Add/remove binary I/O functions for a type;
* Add/remove typmod I/O functions for a type;
* Add/remove a custom ANALYZE statistics functions for a type.

The first of these can be done by the type's owner; all the others
require superuser privilege since misuse could cause problems.

The main motivation for this patch is to allow extensions to
upgrade the feature sets of their data types, so the set of
alterable properties is biased towards that use-case.  However
it's also true that changing some other properties would be
a lot harder, as they get baked into physical storage and/or
stored expressions that depend on the type.

Along the way, refactor GenerateTypeDependencies() to make it easier
to call, refactor DefineType's volatility checks so they can be shared
by AlterType, and teach typcache.c that it might have to reload data
from the type's pg_type row, a scenario it never handled before.
Also rearrange alter_type.sgml a bit for clarity (put the
composite-type operations together).

Tomas Vondra and Tom Lane

Discussion: https://postgr.es/m/20200228004440.b23ein4qvmxnlpht@development
This commit is contained in:
Tom Lane
2020-03-06 12:19:29 -05:00
parent addd034ae1
commit fe30e7ebfa
17 changed files with 970 additions and 149 deletions

View File

@ -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.
*/