Commit fe30e7eb authored by Tom Lane's avatar Tom Lane

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
parent addd034a
...@@ -7828,28 +7828,38 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l ...@@ -7828,28 +7828,38 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
types (those with <structfield>typlen</structfield> = -1) if types (those with <structfield>typlen</structfield> = -1) if
the type is prepared for toasting and what the default strategy the type is prepared for toasting and what the default strategy
for attributes of this type should be. for attributes of this type should be.
Possible values are Possible values are:
<itemizedlist> <itemizedlist>
<listitem> <listitem>
<para><literal>p</literal>: Value must always be stored plain.</para> <para>
<literal>p</literal> (plain): Values must always be stored plain
(non-varlena types always use this value).
</para>
</listitem> </listitem>
<listitem> <listitem>
<para> <para>
<literal>e</literal>: Value can be stored in a <quote>secondary</quote> <literal>e</literal> (external): Values can be stored in a
relation (if relation has one, see secondary <quote>TOAST</quote> relation (if relation has one, see
<literal>pg_class.reltoastrelid</literal>). <literal>pg_class.reltoastrelid</literal>).
</para> </para>
</listitem> </listitem>
<listitem> <listitem>
<para><literal>m</literal>: Value can be stored compressed inline.</para> <para>
<literal>m</literal> (main): Values can be compressed and stored
inline.
</para>
</listitem> </listitem>
<listitem> <listitem>
<para><literal>x</literal>: Value can be stored compressed inline or stored in <quote>secondary</quote> storage.</para> <para>
<literal>x</literal> (extended): Values can be compressed and/or
moved to a secondary relation.
</para>
</listitem> </listitem>
</itemizedlist> </itemizedlist>
Note that <literal>m</literal> columns can also be moved out to secondary <literal>x</literal> is the usual choice for toast-able types.
storage, but only as a last resort (<literal>e</literal> and <literal>x</literal> columns are Note that <literal>m</literal> values can also be moved out to
moved first). secondary storage, but only as a last resort (<literal>e</literal>
and <literal>x</literal> values are moved first).
</para></entry> </para></entry>
</row> </row>
......
This diff is collapsed.
...@@ -155,8 +155,8 @@ TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId) ...@@ -155,8 +155,8 @@ TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId)
* Create dependencies. We can/must skip this in bootstrap mode. * Create dependencies. We can/must skip this in bootstrap mode.
*/ */
if (!IsBootstrapProcessingMode()) if (!IsBootstrapProcessingMode())
GenerateTypeDependencies(typoid, GenerateTypeDependencies(tup,
(Form_pg_type) GETSTRUCT(tup), pg_type_desc,
NULL, NULL,
NULL, NULL,
0, 0,
...@@ -488,8 +488,8 @@ TypeCreate(Oid newTypeOid, ...@@ -488,8 +488,8 @@ TypeCreate(Oid newTypeOid,
* Create dependencies. We can/must skip this in bootstrap mode. * Create dependencies. We can/must skip this in bootstrap mode.
*/ */
if (!IsBootstrapProcessingMode()) if (!IsBootstrapProcessingMode())
GenerateTypeDependencies(typeObjectId, GenerateTypeDependencies(tup,
(Form_pg_type) GETSTRUCT(tup), pg_type_desc,
(defaultTypeBin ? (defaultTypeBin ?
stringToNode(defaultTypeBin) : stringToNode(defaultTypeBin) :
NULL), NULL),
...@@ -516,12 +516,16 @@ TypeCreate(Oid newTypeOid, ...@@ -516,12 +516,16 @@ TypeCreate(Oid newTypeOid,
* GenerateTypeDependencies: build the dependencies needed for a type * GenerateTypeDependencies: build the dependencies needed for a type
* *
* Most of what this function needs to know about the type is passed as the * 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 * new pg_type row, typeTuple. We make callers pass the pg_type Relation
* that, so defaultExpr and typacl are passed separately. (typacl is really * 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.) * "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, * relationKind and isImplicitArray are likewise somewhat expensive to deduce
* so they're also passed separately. * 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; * isDependentType is true if this is an implicit array or relation rowtype;
* that means it doesn't need its own dependencies on owner etc. * that means it doesn't need its own dependencies on owner etc.
...@@ -535,8 +539,8 @@ TypeCreate(Oid newTypeOid, ...@@ -535,8 +539,8 @@ TypeCreate(Oid newTypeOid,
* that type will become a member of the extension.) * that type will become a member of the extension.)
*/ */
void void
GenerateTypeDependencies(Oid typeObjectId, GenerateTypeDependencies(HeapTuple typeTuple,
Form_pg_type typeForm, Relation typeCatalog,
Node *defaultExpr, Node *defaultExpr,
void *typacl, void *typacl,
char relationKind, /* only for relation rowtypes */ char relationKind, /* only for relation rowtypes */
...@@ -544,9 +548,30 @@ GenerateTypeDependencies(Oid typeObjectId, ...@@ -544,9 +548,30 @@ GenerateTypeDependencies(Oid typeObjectId,
bool isDependentType, bool isDependentType,
bool rebuild) bool rebuild)
{ {
Form_pg_type typeForm = (Form_pg_type) GETSTRUCT(typeTuple);
Oid typeObjectId = typeForm->oid;
Datum datum;
bool isNull;
ObjectAddress myself, ObjectAddress myself,
referenced; 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, first flush old dependencies, except extension deps */
if (rebuild) if (rebuild)
{ {
......
This diff is collapsed.
...@@ -3636,6 +3636,17 @@ _copyAlterOperatorStmt(const AlterOperatorStmt *from) ...@@ -3636,6 +3636,17 @@ _copyAlterOperatorStmt(const AlterOperatorStmt *from)
return newnode; return newnode;
} }
static AlterTypeStmt *
_copyAlterTypeStmt(const AlterTypeStmt *from)
{
AlterTypeStmt *newnode = makeNode(AlterTypeStmt);
COPY_NODE_FIELD(typeName);
COPY_NODE_FIELD(options);
return newnode;
}
static RuleStmt * static RuleStmt *
_copyRuleStmt(const RuleStmt *from) _copyRuleStmt(const RuleStmt *from)
{ {
...@@ -5263,6 +5274,9 @@ copyObjectImpl(const void *from) ...@@ -5263,6 +5274,9 @@ copyObjectImpl(const void *from)
case T_AlterOperatorStmt: case T_AlterOperatorStmt:
retval = _copyAlterOperatorStmt(from); retval = _copyAlterOperatorStmt(from);
break; break;
case T_AlterTypeStmt:
retval = _copyAlterTypeStmt(from);
break;
case T_RuleStmt: case T_RuleStmt:
retval = _copyRuleStmt(from); retval = _copyRuleStmt(from);
break; break;
......
...@@ -1481,6 +1481,15 @@ _equalAlterOperatorStmt(const AlterOperatorStmt *a, const AlterOperatorStmt *b) ...@@ -1481,6 +1481,15 @@ _equalAlterOperatorStmt(const AlterOperatorStmt *a, const AlterOperatorStmt *b)
return true; return true;
} }
static bool
_equalAlterTypeStmt(const AlterTypeStmt *a, const AlterTypeStmt *b)
{
COMPARE_NODE_FIELD(typeName);
COMPARE_NODE_FIELD(options);
return true;
}
static bool static bool
_equalRuleStmt(const RuleStmt *a, const RuleStmt *b) _equalRuleStmt(const RuleStmt *a, const RuleStmt *b)
{ {
...@@ -3359,6 +3368,9 @@ equal(const void *a, const void *b) ...@@ -3359,6 +3368,9 @@ equal(const void *a, const void *b)
case T_AlterOperatorStmt: case T_AlterOperatorStmt:
retval = _equalAlterOperatorStmt(a, b); retval = _equalAlterOperatorStmt(a, b);
break; break;
case T_AlterTypeStmt:
retval = _equalAlterTypeStmt(a, b);
break;
case T_RuleStmt: case T_RuleStmt:
retval = _equalRuleStmt(a, b); retval = _equalRuleStmt(a, b);
break; break;
......
...@@ -249,7 +249,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ...@@ -249,7 +249,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
AlterFdwStmt AlterForeignServerStmt AlterGroupStmt AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
AlterObjectDependsStmt AlterObjectSchemaStmt AlterOwnerStmt AlterObjectDependsStmt AlterObjectSchemaStmt AlterOwnerStmt
AlterOperatorStmt AlterSeqStmt AlterSystemStmt AlterTableStmt AlterOperatorStmt AlterTypeStmt AlterSeqStmt AlterSystemStmt AlterTableStmt
AlterTblSpcStmt AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt AlterTblSpcStmt AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt
AlterCompositeTypeStmt AlterUserMappingStmt AlterCompositeTypeStmt AlterUserMappingStmt
AlterRoleStmt AlterRoleSetStmt AlterPolicyStmt AlterStatsStmt AlterRoleStmt AlterRoleSetStmt AlterPolicyStmt AlterStatsStmt
...@@ -847,6 +847,7 @@ stmt : ...@@ -847,6 +847,7 @@ stmt :
| AlterObjectSchemaStmt | AlterObjectSchemaStmt
| AlterOwnerStmt | AlterOwnerStmt
| AlterOperatorStmt | AlterOperatorStmt
| AlterTypeStmt
| AlterPolicyStmt | AlterPolicyStmt
| AlterSeqStmt | AlterSeqStmt
| AlterSystemStmt | AlterSystemStmt
...@@ -9365,6 +9366,24 @@ operator_def_arg: ...@@ -9365,6 +9366,24 @@ operator_def_arg:
| Sconst { $$ = (Node *)makeString($1); } | 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 * ALTER THING name OWNER TO newname
......
...@@ -162,6 +162,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree) ...@@ -162,6 +162,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
case T_AlterTableMoveAllStmt: case T_AlterTableMoveAllStmt:
case T_AlterTableSpaceOptionsStmt: case T_AlterTableSpaceOptionsStmt:
case T_AlterTableStmt: case T_AlterTableStmt:
case T_AlterTypeStmt:
case T_AlterUserMappingStmt: case T_AlterUserMappingStmt:
case T_CommentStmt: case T_CommentStmt:
case T_CompositeTypeStmt: case T_CompositeTypeStmt:
...@@ -1713,6 +1714,10 @@ ProcessUtilitySlow(ParseState *pstate, ...@@ -1713,6 +1714,10 @@ ProcessUtilitySlow(ParseState *pstate,
address = AlterOperator((AlterOperatorStmt *) parsetree); address = AlterOperator((AlterOperatorStmt *) parsetree);
break; break;
case T_AlterTypeStmt:
address = AlterType((AlterTypeStmt *) parsetree);
break;
case T_CommentStmt: case T_CommentStmt:
address = CommentObject((CommentStmt *) parsetree); address = CommentObject((CommentStmt *) parsetree);
break; break;
...@@ -2895,6 +2900,10 @@ CreateCommandTag(Node *parsetree) ...@@ -2895,6 +2900,10 @@ CreateCommandTag(Node *parsetree)
tag = CMDTAG_ALTER_OPERATOR; tag = CMDTAG_ALTER_OPERATOR;
break; break;
case T_AlterTypeStmt:
tag = CMDTAG_ALTER_TYPE;
break;
case T_AlterTSDictionaryStmt: case T_AlterTSDictionaryStmt:
tag = CMDTAG_ALTER_TEXT_SEARCH_DICTIONARY; tag = CMDTAG_ALTER_TEXT_SEARCH_DICTIONARY;
break; break;
...@@ -3251,6 +3260,10 @@ GetCommandLogLevel(Node *parsetree) ...@@ -3251,6 +3260,10 @@ GetCommandLogLevel(Node *parsetree)
lev = LOGSTMT_DDL; lev = LOGSTMT_DDL;
break; break;
case T_AlterTypeStmt:
lev = LOGSTMT_DDL;
break;
case T_AlterTableMoveAllStmt: case T_AlterTableMoveAllStmt:
case T_AlterTableStmt: case T_AlterTableStmt:
lev = LOGSTMT_DDL; lev = LOGSTMT_DDL;
......
...@@ -23,11 +23,12 @@ ...@@ -23,11 +23,12 @@
* permanently allows caching pointers to them in long-lived places. * permanently allows caching pointers to them in long-lived places.
* *
* We have some provisions for updating cache entries if the stored data * We have some provisions for updating cache entries if the stored data
* becomes obsolete. Information dependent on opclasses is cleared if we * becomes obsolete. Core data extracted from the pg_type row is updated
* detect updates to pg_opclass. We also support clearing the tuple * when we detect updates to pg_type. Information dependent on opclasses is
* descriptor and operator/function parts of a rowtype's cache entry, * cleared if we detect updates to pg_opclass. We also support clearing the
* since those may need to change as a consequence of ALTER TABLE. * tuple descriptor and operator/function parts of a rowtype's cache entry,
* Domain constraint changes are also tracked properly. * 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 * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
...@@ -80,24 +81,31 @@ static HTAB *TypeCacheHash = NULL; ...@@ -80,24 +81,31 @@ static HTAB *TypeCacheHash = NULL;
static TypeCacheEntry *firstDomainTypeEntry = NULL; static TypeCacheEntry *firstDomainTypeEntry = NULL;
/* Private flag bits in the TypeCacheEntry.flags field */ /* Private flag bits in the TypeCacheEntry.flags field */
#define TCFLAGS_CHECKED_BTREE_OPCLASS 0x000001 #define TCFLAGS_HAVE_PG_TYPE_DATA 0x000001
#define TCFLAGS_CHECKED_HASH_OPCLASS 0x000002 #define TCFLAGS_CHECKED_BTREE_OPCLASS 0x000002
#define TCFLAGS_CHECKED_EQ_OPR 0x000004 #define TCFLAGS_CHECKED_HASH_OPCLASS 0x000004
#define TCFLAGS_CHECKED_LT_OPR 0x000008 #define TCFLAGS_CHECKED_EQ_OPR 0x000008
#define TCFLAGS_CHECKED_GT_OPR 0x000010 #define TCFLAGS_CHECKED_LT_OPR 0x000010
#define TCFLAGS_CHECKED_CMP_PROC 0x000020 #define TCFLAGS_CHECKED_GT_OPR 0x000020
#define TCFLAGS_CHECKED_HASH_PROC 0x000040 #define TCFLAGS_CHECKED_CMP_PROC 0x000040
#define TCFLAGS_CHECKED_HASH_EXTENDED_PROC 0x000080 #define TCFLAGS_CHECKED_HASH_PROC 0x000080
#define TCFLAGS_CHECKED_ELEM_PROPERTIES 0x000100 #define TCFLAGS_CHECKED_HASH_EXTENDED_PROC 0x000100
#define TCFLAGS_HAVE_ELEM_EQUALITY 0x000200 #define TCFLAGS_CHECKED_ELEM_PROPERTIES 0x000200
#define TCFLAGS_HAVE_ELEM_COMPARE 0x000400 #define TCFLAGS_HAVE_ELEM_EQUALITY 0x000400
#define TCFLAGS_HAVE_ELEM_HASHING 0x000800 #define TCFLAGS_HAVE_ELEM_COMPARE 0x000800
#define TCFLAGS_HAVE_ELEM_EXTENDED_HASHING 0x001000 #define TCFLAGS_HAVE_ELEM_HASHING 0x001000
#define TCFLAGS_CHECKED_FIELD_PROPERTIES 0x002000 #define TCFLAGS_HAVE_ELEM_EXTENDED_HASHING 0x002000
#define TCFLAGS_HAVE_FIELD_EQUALITY 0x004000 #define TCFLAGS_CHECKED_FIELD_PROPERTIES 0x004000
#define TCFLAGS_HAVE_FIELD_COMPARE 0x008000 #define TCFLAGS_HAVE_FIELD_EQUALITY 0x008000
#define TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS 0x010000 #define TCFLAGS_HAVE_FIELD_COMPARE 0x010000
#define TCFLAGS_DOMAIN_BASE_IS_COMPOSITE 0x020000 #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 * 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); ...@@ -295,6 +303,7 @@ static bool range_element_has_hashing(TypeCacheEntry *typentry);
static bool range_element_has_extended_hashing(TypeCacheEntry *typentry); static bool range_element_has_extended_hashing(TypeCacheEntry *typentry);
static void cache_range_element_properties(TypeCacheEntry *typentry); static void cache_range_element_properties(TypeCacheEntry *typentry);
static void TypeCacheRelCallback(Datum arg, Oid relid); 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 TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue);
static void TypeCacheConstrCallback(Datum arg, int cacheid, uint32 hashvalue); static void TypeCacheConstrCallback(Datum arg, int cacheid, uint32 hashvalue);
static void load_enum_cache_data(TypeCacheEntry *tcache); static void load_enum_cache_data(TypeCacheEntry *tcache);
...@@ -337,9 +346,9 @@ lookup_type_cache(Oid type_id, int flags) ...@@ -337,9 +346,9 @@ lookup_type_cache(Oid type_id, int flags)
/* Also set up callbacks for SI invalidations */ /* Also set up callbacks for SI invalidations */
CacheRegisterRelcacheCallback(TypeCacheRelCallback, (Datum) 0); CacheRegisterRelcacheCallback(TypeCacheRelCallback, (Datum) 0);
CacheRegisterSyscacheCallback(TYPEOID, TypeCacheTypCallback, (Datum) 0);
CacheRegisterSyscacheCallback(CLAOID, TypeCacheOpcCallback, (Datum) 0); CacheRegisterSyscacheCallback(CLAOID, TypeCacheOpcCallback, (Datum) 0);
CacheRegisterSyscacheCallback(CONSTROID, TypeCacheConstrCallback, (Datum) 0); CacheRegisterSyscacheCallback(CONSTROID, TypeCacheConstrCallback, (Datum) 0);
CacheRegisterSyscacheCallback(TYPEOID, TypeCacheConstrCallback, (Datum) 0);
/* Also make sure CacheMemoryContext exists */ /* Also make sure CacheMemoryContext exists */
if (!CacheMemoryContext) if (!CacheMemoryContext)
...@@ -381,7 +390,13 @@ lookup_type_cache(Oid type_id, int flags) ...@@ -381,7 +390,13 @@ lookup_type_cache(Oid type_id, int flags)
Assert(!found); /* it wasn't there a moment ago */ Assert(!found); /* it wasn't there a moment ago */
MemSet(typentry, 0, sizeof(TypeCacheEntry)); MemSet(typentry, 0, sizeof(TypeCacheEntry));
/* These fields can never change, by definition */
typentry->type_id = type_id; 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->typlen = typtup->typlen;
typentry->typbyval = typtup->typbyval; typentry->typbyval = typtup->typbyval;
typentry->typalign = typtup->typalign; typentry->typalign = typtup->typalign;
...@@ -390,6 +405,7 @@ lookup_type_cache(Oid type_id, int flags) ...@@ -390,6 +405,7 @@ lookup_type_cache(Oid type_id, int flags)
typentry->typrelid = typtup->typrelid; typentry->typrelid = typtup->typrelid;
typentry->typelem = typtup->typelem; typentry->typelem = typtup->typelem;
typentry->typcollation = typtup->typcollation; 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 it's a domain, immediately thread it into the domain cache list */
if (typentry->typtype == TYPTYPE_DOMAIN) if (typentry->typtype == TYPTYPE_DOMAIN)
...@@ -400,6 +416,43 @@ lookup_type_cache(Oid type_id, int flags) ...@@ -400,6 +416,43 @@ lookup_type_cache(Oid type_id, int flags)
ReleaseSysCache(tp); 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 * 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) ...@@ -750,12 +803,17 @@ lookup_type_cache(Oid type_id, int flags)
/* /*
* If requested, get information about a range type * 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) && if ((flags & TYPECACHE_RANGE_INFO) &&
typentry->rngelemtype == NULL &&
typentry->typtype == TYPTYPE_RANGE) typentry->typtype == TYPTYPE_RANGE)
{ {
if (typentry->rngelemtype == NULL)
load_rangetype_info(typentry); 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) ...@@ -2129,7 +2187,7 @@ TypeCacheRelCallback(Datum arg, Oid relid)
} }
/* Reset equality/comparison/hashing validity information */ /* Reset equality/comparison/hashing validity information */
typentry->flags = 0; typentry->flags &= ~TCFLAGS_OPERATOR_FLAGS;
} }
else if (typentry->typtype == TYPTYPE_DOMAIN) else if (typentry->typtype == TYPTYPE_DOMAIN)
{ {
...@@ -2140,7 +2198,39 @@ TypeCacheRelCallback(Datum arg, Oid relid) ...@@ -2140,7 +2198,39 @@ TypeCacheRelCallback(Datum arg, Oid relid)
* type is composite, we don't need to reset anything. * type is composite, we don't need to reset anything.
*/ */
if (typentry->flags & TCFLAGS_DOMAIN_BASE_IS_COMPOSITE) 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) ...@@ -2172,7 +2262,7 @@ TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue)
while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL) while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL)
{ {
/* Reset equality/comparison/hashing validity information */ /* 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) ...@@ -2181,12 +2271,12 @@ TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue)
* Syscache inval callback function * Syscache inval callback function
* *
* This is called when a syscache invalidation event occurs for any * This is called when a syscache invalidation event occurs for any
* pg_constraint or pg_type row. We flush information about domain * pg_constraint row. We flush information about domain constraints
* constraints when this happens. * when this happens.
* *
* It's slightly annoying that we can't tell whether the inval event was for a * It's slightly annoying that we can't tell whether the inval event was for
* domain constraint/type record or not; there's usually more update traffic * a domain constraint record or not; there's usually more update traffic
* for table constraints/types than domain constraints, so we'll do a lot of * 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 * useless flushes. Still, this is better than the old no-caching-at-all
* approach to domain constraints. * approach to domain constraints.
*/ */
......
...@@ -2150,7 +2150,7 @@ psql_completion(const char *text, int start, int end) ...@@ -2150,7 +2150,7 @@ psql_completion(const char *text, int start, int end)
else if (Matches("ALTER", "TYPE", MatchAny)) else if (Matches("ALTER", "TYPE", MatchAny))
COMPLETE_WITH("ADD ATTRIBUTE", "ADD VALUE", "ALTER ATTRIBUTE", COMPLETE_WITH("ADD ATTRIBUTE", "ADD VALUE", "ALTER ATTRIBUTE",
"DROP ATTRIBUTE", "DROP ATTRIBUTE",
"OWNER TO", "RENAME", "SET SCHEMA"); "OWNER TO", "RENAME", "SET SCHEMA", "SET (");
/* complete ALTER TYPE <foo> ADD with actions */ /* complete ALTER TYPE <foo> ADD with actions */
else if (Matches("ALTER", "TYPE", MatchAny, "ADD")) else if (Matches("ALTER", "TYPE", MatchAny, "ADD"))
COMPLETE_WITH("ATTRIBUTE", "VALUE"); COMPLETE_WITH("ATTRIBUTE", "VALUE");
......
...@@ -340,8 +340,8 @@ extern ObjectAddress TypeCreate(Oid newTypeOid, ...@@ -340,8 +340,8 @@ extern ObjectAddress TypeCreate(Oid newTypeOid,
bool typeNotNull, bool typeNotNull,
Oid typeCollation); Oid typeCollation);
extern void GenerateTypeDependencies(Oid typeObjectId, extern void GenerateTypeDependencies(HeapTuple typeTuple,
Form_pg_type typeForm, Relation typeCatalog,
Node *defaultExpr, Node *defaultExpr,
void *typacl, void *typacl,
char relationKind, /* only for relation char relationKind, /* only for relation
......
...@@ -54,4 +54,6 @@ extern Oid AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid, ...@@ -54,4 +54,6 @@ extern Oid AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid,
bool errorOnTableType, bool errorOnTableType,
ObjectAddresses *objsMoved); ObjectAddresses *objsMoved);
extern ObjectAddress AlterType(AlterTypeStmt *stmt);
#endif /* TYPECMDS_H */ #endif /* TYPECMDS_H */
...@@ -380,6 +380,7 @@ typedef enum NodeTag ...@@ -380,6 +380,7 @@ typedef enum NodeTag
T_AlterObjectSchemaStmt, T_AlterObjectSchemaStmt,
T_AlterOwnerStmt, T_AlterOwnerStmt,
T_AlterOperatorStmt, T_AlterOperatorStmt,
T_AlterTypeStmt,
T_DropOwnedStmt, T_DropOwnedStmt,
T_ReassignOwnedStmt, T_ReassignOwnedStmt,
T_CompositeTypeStmt, T_CompositeTypeStmt,
......
...@@ -2959,9 +2959,8 @@ typedef struct AlterOwnerStmt ...@@ -2959,9 +2959,8 @@ typedef struct AlterOwnerStmt
RoleSpec *newowner; /* the new owner */ RoleSpec *newowner; /* the new owner */
} AlterOwnerStmt; } AlterOwnerStmt;
/* ---------------------- /* ----------------------
* Alter Operator Set Restrict, Join * Alter Operator Set ( this-n-that )
* ---------------------- * ----------------------
*/ */
typedef struct AlterOperatorStmt typedef struct AlterOperatorStmt
...@@ -2971,6 +2970,16 @@ typedef struct AlterOperatorStmt ...@@ -2971,6 +2970,16 @@ typedef struct AlterOperatorStmt
List *options; /* List of DefElem nodes */ List *options; /* List of DefElem nodes */
} AlterOperatorStmt; } 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 * Create Rule Statement
......
...@@ -33,6 +33,8 @@ typedef struct TypeCacheEntry ...@@ -33,6 +33,8 @@ typedef struct TypeCacheEntry
/* typeId is the hash lookup key and MUST BE FIRST */ /* typeId is the hash lookup key and MUST BE FIRST */
Oid type_id; /* OID of the data type */ 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 */ /* some subsidiary information copied from the pg_type row */
int16 typlen; int16 typlen;
bool typbyval; bool typbyval;
......
...@@ -224,3 +224,81 @@ select format_type('bpchar'::regtype, -1); ...@@ -224,3 +224,81 @@ select format_type('bpchar'::regtype, -1);
bpchar bpchar
(1 row) (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
...@@ -166,3 +166,60 @@ select format_type('varchar'::regtype, 42); ...@@ -166,3 +166,60 @@ select format_type('varchar'::regtype, 42);
select format_type('bpchar'::regtype, null); select format_type('bpchar'::regtype, null);
-- this behavior difference is intentional -- this behavior difference is intentional
select format_type('bpchar'::regtype, -1); 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;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment