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

@ -7828,28 +7828,38 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
types (those with <structfield>typlen</structfield> = -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:
<itemizedlist>
<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>
<para>
<literal>e</literal>: Value can be stored in a <quote>secondary</quote>
relation (if relation has one, see
<literal>e</literal> (external): Values can be stored in a
secondary <quote>TOAST</quote> relation (if relation has one, see
<literal>pg_class.reltoastrelid</literal>).
</para>
</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>
<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>
</itemizedlist>
Note that <literal>m</literal> columns can also be moved out to secondary
storage, but only as a last resort (<literal>e</literal> and <literal>x</literal> columns are
moved first).
<literal>x</literal> is the usual choice for toast-able types.
Note that <literal>m</literal> values can also be moved out to
secondary storage, but only as a last resort (<literal>e</literal>
and <literal>x</literal> values are moved first).
</para></entry>
</row>

View File

@ -23,13 +23,14 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
ALTER TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">action</replaceable> [, ... ]
ALTER TYPE <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable class="parameter">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
ALTER TYPE <replaceable class="parameter">name</replaceable> RENAME ATTRIBUTE <replaceable class="parameter">attribute_name</replaceable> TO <replaceable class="parameter">new_attribute_name</replaceable> [ CASCADE | RESTRICT ]
ALTER TYPE <replaceable class="parameter">name</replaceable> RENAME TO <replaceable class="parameter">new_name</replaceable>
ALTER TYPE <replaceable class="parameter">name</replaceable> SET SCHEMA <replaceable class="parameter">new_schema</replaceable>
ALTER TYPE <replaceable class="parameter">name</replaceable> RENAME ATTRIBUTE <replaceable class="parameter">attribute_name</replaceable> TO <replaceable class="parameter">new_attribute_name</replaceable> [ CASCADE | RESTRICT ]
ALTER TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">action</replaceable> [, ... ]
ALTER TYPE <replaceable class="parameter">name</replaceable> ADD VALUE [ IF NOT EXISTS ] <replaceable class="parameter">new_enum_value</replaceable> [ { BEFORE | AFTER } <replaceable class="parameter">neighbor_enum_value</replaceable> ]
ALTER TYPE <replaceable class="parameter">name</replaceable> RENAME VALUE <replaceable class="parameter">existing_enum_value</replaceable> TO <replaceable class="parameter">new_enum_value</replaceable>
ALTER TYPE <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">property</replaceable> = <replaceable class="parameter">value</replaceable> [, ... ] )
<phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
@ -47,6 +48,43 @@ ALTER TYPE <replaceable class="parameter">name</replaceable> RENAME VALUE <repla
There are several subforms:
<variablelist>
<varlistentry>
<term><literal>OWNER</literal></term>
<listitem>
<para>
This form changes the owner of the type.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>RENAME</literal></term>
<listitem>
<para>
This form changes the name of the type.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>SET SCHEMA</literal></term>
<listitem>
<para>
This form moves the type into another schema.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>RENAME ATTRIBUTE</literal></term>
<listitem>
<para>
This form is only usable with composite types.
It changes the name of an individual attribute of the type.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>ADD ATTRIBUTE</literal></term>
<listitem>
@ -70,7 +108,7 @@ ALTER TYPE <replaceable class="parameter">name</replaceable> RENAME VALUE <repla
</varlistentry>
<varlistentry>
<term><literal>SET DATA TYPE</literal></term>
<term><literal>ALTER ATTRIBUTE ... SET DATA TYPE</literal></term>
<listitem>
<para>
This form changes the type of an attribute of a composite type.
@ -78,34 +116,6 @@ ALTER TYPE <replaceable class="parameter">name</replaceable> RENAME VALUE <repla
</listitem>
</varlistentry>
<varlistentry>
<term><literal>OWNER</literal></term>
<listitem>
<para>
This form changes the owner of the type.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>RENAME</literal></term>
<listitem>
<para>
This form changes the name of the type or the name of an
individual attribute of a composite type.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>SET SCHEMA</literal></term>
<listitem>
<para>
This form moves the type into another schema.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>ADD VALUE [ IF NOT EXISTS ] [ BEFORE | AFTER ]</literal></term>
<listitem>
@ -135,6 +145,84 @@ ALTER TYPE <replaceable class="parameter">name</replaceable> RENAME VALUE <repla
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<literal>SET ( <replaceable class="parameter">property</replaceable> = <replaceable class="parameter">value</replaceable> [, ... ] )</literal>
</term>
<listitem>
<para>
This form is only applicable to base types. It allows adjustment of a
subset of the base-type properties that can be set in <command>CREATE
TYPE</command>. Specifically, these properties can be changed:
<itemizedlist>
<listitem>
<para>
<literal>RECEIVE</literal> can be set to the name of a binary input
function, or <literal>NONE</literal> to remove the type's binary
input function. Using this option requires superuser privilege.
</para>
</listitem>
<listitem>
<para>
<literal>SEND</literal> can be set to the name of a binary output
function, or <literal>NONE</literal> to remove the type's binary
output function. Using this option requires superuser privilege.
</para>
</listitem>
<listitem>
<para>
<literal>TYPMOD_IN</literal> can be set to the name of a type
modifier input function, or <literal>NONE</literal> to remove the
type's type modifier input function. Using this option requires
superuser privilege.
</para>
</listitem>
<listitem>
<para>
<literal>TYPMOD_OUT</literal> can be set to the name of a type
modifier output function, or <literal>NONE</literal> to remove the
type's type modifier output function. Using this option requires
superuser privilege.
</para>
</listitem>
<listitem>
<para>
<literal>ANALYZE</literal> can be set to the name of a type-specific
statistics collection function, or <literal>NONE</literal> to remove
the type's statistics collection function. Using this option
requires superuser privilege.
</para>
</listitem>
<listitem>
<para>
<literal>STORAGE</literal><indexterm>
<primary>TOAST</primary>
<secondary>per-type storage settings</secondary>
</indexterm>
can be set to <literal>plain</literal>,
<literal>extended</literal>, <literal>external</literal>,
or <literal>main</literal> (see <xref linkend="storage-toast"/> for
more information about what these mean). However, changing
from <literal>plain</literal> to another setting requires superuser
privilege (because it requires that the type's C functions all be
TOAST-ready), and changing to <literal>plain</literal> 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 <xref linkend="sql-altertable"/> to change the TOAST
strategy for existing table columns.
</para>
</listitem>
</itemizedlist>
See <xref linkend="sql-createtype"/> 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.
</para>
</listitem>
</varlistentry>
</variablelist>
</para>
@ -156,7 +244,7 @@ ALTER TYPE <replaceable class="parameter">name</replaceable> RENAME VALUE <repla
doesn't do anything you couldn't do by dropping and recreating the type.
However, a superuser can alter ownership of any type anyway.)
To add an attribute or alter an attribute type, you must also
have <literal>USAGE</literal> privilege on the data type.
have <literal>USAGE</literal> privilege on the attribute's data type.
</para>
</refsect1>
@ -262,6 +350,16 @@ ALTER TYPE <replaceable class="parameter">name</replaceable> RENAME VALUE <repla
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="parameter">property</replaceable></term>
<listitem>
<para>
The name of a base-type property to be modified; see above for
possible values.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>CASCADE</literal></term>
<listitem>
@ -336,7 +434,7 @@ ALTER TYPE email SET SCHEMA customers;
</para>
<para>
To add a new attribute to a type:
To add a new attribute to a composite type:
<programlisting>
ALTER TYPE compfoo ADD ATTRIBUTE f3 int;
</programlisting>
@ -353,7 +451,20 @@ ALTER TYPE colors ADD VALUE 'orange' AFTER 'red';
To rename an enum value:
<programlisting>
ALTER TYPE colors RENAME VALUE 'purple' TO 'mauve';
</programlisting></para>
</programlisting>
</para>
<para>
To create binary I/O functions for an existing base type:
<programlisting>
CREATE FUNCTION mytypesend(mytype) RETURNS bytea ...;
CREATE FUNCTION mytyperecv(internal, oid, integer) RETURNS mytype ...;
ALTER TYPE mytype SET (
SEND = mytypesend,
RECEIVE = mytyperecv
);
</programlisting>
</para>
</refsect1>
<refsect1>

View File

@ -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)
{

View File

@ -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 <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);
}

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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;

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

View File

@ -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 <foo> ADD with actions */
else if (Matches("ALTER", "TYPE", MatchAny, "ADD"))
COMPLETE_WITH("ATTRIBUTE", "VALUE");

View File

@ -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

View File

@ -54,4 +54,6 @@ extern Oid AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid,
bool errorOnTableType,
ObjectAddresses *objsMoved);
extern ObjectAddress AlterType(AlterTypeStmt *stmt);
#endif /* TYPECMDS_H */

View File

@ -380,6 +380,7 @@ typedef enum NodeTag
T_AlterObjectSchemaStmt,
T_AlterOwnerStmt,
T_AlterOperatorStmt,
T_AlterTypeStmt,
T_DropOwnedStmt,
T_ReassignOwnedStmt,
T_CompositeTypeStmt,

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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;