Re: Allowing ALTER TYPE to change storage strategy - Mailing list pgsql-hackers
From | Tom Lane |
---|---|
Subject | Re: Allowing ALTER TYPE to change storage strategy |
Date | |
Msg-id | 30065.1583347198@sss.pgh.pa.us Whole thread Raw |
In response to | Re: Allowing ALTER TYPE to change storage strategy (Tomas Vondra <tomas.vondra@2ndquadrant.com>) |
Responses |
Re: Allowing ALTER TYPE to change storage strategy
|
List | pgsql-hackers |
Tomas Vondra <tomas.vondra@2ndquadrant.com> writes: > On Mon, Mar 02, 2020 at 02:11:10PM -0500, Tom Lane wrote: >> In the v3 patch below, I've ripped out the ALTER DOMAIN syntax on >> the assumption that we'd do the latter; but I've not written the >> cascade recursion logic, because that seemed like a lot of work >> to do in advance of having consensus on it being a good idea. > I do agree we should do the latter, i.e. maintain the assumption that > domains have the same properties as their base type. I can't think of a > use case for allowing them to differ, it just didn't occur to me there > is this implicit assumption when writing the patch. Here's a v4 that is rebased over HEAD + the OPAQUE-ectomy that I proposed at <4110.1583255415@sss.pgh.pa.us>, plus it adds recursion to domains, and I also added the ability to set typmod I/O and analyze functions, which seems like functionality that somebody could possibly wish to add to a type after-the-fact much like binary I/O. I thought about allowing the basic I/O functions to be replaced as well, but I couldn't really convince myself that there's a use-case for that. In practice you'd probably always just change the behavior of the existing I/O functions, not want to sub in new ones. (I kind of wonder, actually, whether there's a use-case for the NONE options here at all. When would you remove a support function?) Of the remaining CREATE TYPE options, "category" and "preferred" could perhaps be changeable but I couldn't get excited about them. All the others seem like there are gotchas --- for example, changing a type's collatable property is much harder than it looks because it'd affect stored views. So this seems like a reasonable stopping point. I think this is committable --- how about you? I've included the OPAQUE-ectomy patches below so that the cfbot can test this, but they're the same as in the other thread. regards, tom lane diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 175315f..111f8e6 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -823,18 +823,6 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> function is written in C. </para> - <para> - In <productname>PostgreSQL</productname> versions before 7.3, it - was customary to avoid creating a shell type at all, by replacing the - functions' forward references to the type name with the placeholder - pseudo-type <type>opaque</type>. The <type>cstring</type> arguments and - results also had to be declared as <type>opaque</type> before 7.3. To - support loading of old dump files, <command>CREATE TYPE</command> will - accept I/O functions declared using <type>opaque</type>, but it will issue - a notice and change the function declarations to use the correct - types. - </para> - </refsect1> <refsect1> diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index 540044b..9279c05 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -1444,50 +1444,6 @@ SetFunctionReturnType(Oid funcOid, Oid newRetType) /* - * SetFunctionArgType - change declared argument type of a function - * - * As above, but change an argument's type. - */ -void -SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType) -{ - Relation pg_proc_rel; - HeapTuple tup; - Form_pg_proc procForm; - ObjectAddress func_address; - ObjectAddress type_address; - - pg_proc_rel = table_open(ProcedureRelationId, RowExclusiveLock); - - tup = SearchSysCacheCopy1(PROCOID, ObjectIdGetDatum(funcOid)); - if (!HeapTupleIsValid(tup)) /* should not happen */ - elog(ERROR, "cache lookup failed for function %u", funcOid); - procForm = (Form_pg_proc) GETSTRUCT(tup); - - if (argIndex < 0 || argIndex >= procForm->pronargs || - procForm->proargtypes.values[argIndex] != OPAQUEOID) - elog(ERROR, "function %u doesn't take OPAQUE", funcOid); - - /* okay to overwrite copied tuple */ - procForm->proargtypes.values[argIndex] = newArgType; - - /* update the catalog and its indexes */ - CatalogTupleUpdate(pg_proc_rel, &tup->t_self, tup); - - table_close(pg_proc_rel, RowExclusiveLock); - - /* - * Also update the dependency to the new type. Opaque is a pinned type, so - * there is no old dependency record for it that we would need to remove. - */ - ObjectAddressSet(type_address, TypeRelationId, newArgType); - ObjectAddressSet(func_address, ProcedureRelationId, funcOid); - recordDependencyOn(&func_address, &type_address, DEPENDENCY_NORMAL); -} - - - -/* * CREATE CAST */ ObjectAddress diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 5209736..d6e694e 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -163,7 +163,6 @@ DefineType(ParseState *pstate, List *names, List *parameters) char *array_type; Oid array_oid; Oid typoid; - Oid resulttype; ListCell *pl; ObjectAddress address; @@ -196,8 +195,7 @@ DefineType(ParseState *pstate, List *names, List *parameters) #endif /* - * Look to see if type already exists (presumably as a shell; if not, - * TypeCreate will complain). + * Look to see if type already exists. */ typoid = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid, CStringGetDatum(typeName), @@ -211,35 +209,37 @@ DefineType(ParseState *pstate, List *names, List *parameters) { if (moveArrayTypeName(typoid, typeName, typeNamespace)) typoid = InvalidOid; + else + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("type \"%s\" already exists", typeName))); } /* - * If it doesn't exist, create it as a shell, so that the OID is known for - * use in the I/O function definitions. + * If this command is a parameterless CREATE TYPE, then we're just here to + * make a shell type, so do that (or fail if there already is a shell). */ - if (!OidIsValid(typoid)) + if (parameters == NIL) { - address = TypeShellMake(typeName, typeNamespace, GetUserId()); - typoid = address.objectId; - /* Make new shell type visible for modification below */ - CommandCounterIncrement(); - - /* - * If the command was a parameterless CREATE TYPE, we're done --- - * creating the shell type was all we're supposed to do. - */ - if (parameters == NIL) - return address; - } - else - { - /* Complain if dummy CREATE TYPE and entry already exists */ - if (parameters == NIL) + if (OidIsValid(typoid)) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("type \"%s\" already exists", typeName))); + + address = TypeShellMake(typeName, typeNamespace, GetUserId()); + return address; } + /* + * Otherwise, we must already have a shell type, since there is no other + * way that the I/O functions could have been created. + */ + if (!OidIsValid(typoid)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("type \"%s\" does not exist", typeName), + errhint("Create the type as a shell type, then create its I/O functions, then do a full CREATE TYPE."))); + /* Extract the parameters from the parameter list */ foreach(pl, parameters) { @@ -445,63 +445,6 @@ DefineType(ParseState *pstate, List *names, List *parameters) sendOid = findTypeSendFunction(sendName, typoid); /* - * Verify that I/O procs return the expected thing. If we see OPAQUE, - * complain and change it to the correct type-safe choice. - */ - resulttype = get_func_rettype(inputOid); - if (resulttype != typoid) - { - if (resulttype == OPAQUEOID) - { - /* backwards-compatibility hack */ - ereport(WARNING, - (errmsg("changing return type of function %s from %s to %s", - NameListToString(inputName), "opaque", typeName))); - SetFunctionReturnType(inputOid, typoid); - } - else - ereport(ERROR, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("type input function %s must return type %s", - NameListToString(inputName), typeName))); - } - resulttype = get_func_rettype(outputOid); - if (resulttype != CSTRINGOID) - { - if (resulttype == OPAQUEOID) - { - /* backwards-compatibility hack */ - ereport(WARNING, - (errmsg("changing return type of function %s from %s to %s", - NameListToString(outputName), "opaque", "cstring"))); - SetFunctionReturnType(outputOid, CSTRINGOID); - } - else - ereport(ERROR, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("type output function %s must return type %s", - NameListToString(outputName), "cstring"))); - } - if (receiveOid) - { - resulttype = get_func_rettype(receiveOid); - if (resulttype != typoid) - ereport(ERROR, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("type receive function %s must return type %s", - NameListToString(receiveName), typeName))); - } - if (sendOid) - { - resulttype = get_func_rettype(sendOid); - if (resulttype != BYTEAOID) - ereport(ERROR, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("type send function %s must return type %s", - NameListToString(sendName), "bytea"))); - } - - /* * Convert typmodin/out function proc names to OIDs. */ if (typmodinName) @@ -1404,16 +1347,9 @@ DefineRange(CreateRangeStmt *stmt) } /* - * If it doesn't exist, create it as a shell, so that the OID is known for - * use in the range function definitions. + * Unlike DefineType(), we don't insist on a shell type existing first, as + * it's only needed if the user wants to specify a canonical function. */ - if (!OidIsValid(typoid)) - { - address = TypeShellMake(typeName, typeNamespace, GetUserId()); - typoid = address.objectId; - /* Make new shell type visible for modification below */ - CommandCounterIncrement(); - } /* Extract the parameters from the parameter list */ foreach(lc, stmt->params) @@ -1502,8 +1438,15 @@ DefineRange(CreateRangeStmt *stmt) /* Identify support functions, if provided */ if (rangeCanonicalName != NIL) + { + if (!OidIsValid(typoid)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("cannot specify a canonical function without a pre-created shell type"), + errhint("Create the type as a shell type, then create its canonicalization function, then do a fullCREATE TYPE."))); rangeCanonical = findRangeCanonicalFunction(rangeCanonicalName, typoid); + } else rangeCanonical = InvalidOid; @@ -1555,7 +1498,8 @@ DefineRange(CreateRangeStmt *stmt) 0, /* Array dimensions of typbasetype */ false, /* Type NOT NULL */ InvalidOid); /* type's collation (ranges never have one) */ - Assert(typoid == address.objectId); + Assert(typoid == InvalidOid || typoid == address.objectId); + typoid = address.objectId; /* Create the entry in pg_range */ RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass, @@ -1695,63 +1639,32 @@ findTypeInputFunction(List *procname, Oid typeOid) /* * Input functions can take a single argument of type CSTRING, or three - * arguments (string, typioparam OID, typmod). - * - * For backwards compatibility we allow OPAQUE in place of CSTRING; if we - * see this, we issue a warning and fix up the pg_proc entry. + * arguments (string, typioparam OID, typmod). They must return the + * target type. */ argList[0] = CSTRINGOID; procOid = LookupFuncName(procname, 1, argList, true); - if (OidIsValid(procOid)) - return procOid; - - argList[1] = OIDOID; - argList[2] = INT4OID; - - procOid = LookupFuncName(procname, 3, argList, true); - if (OidIsValid(procOid)) - return procOid; - - /* No luck, try it with OPAQUE */ - argList[0] = OPAQUEOID; - - procOid = LookupFuncName(procname, 1, argList, true); - if (!OidIsValid(procOid)) { argList[1] = OIDOID; argList[2] = INT4OID; procOid = LookupFuncName(procname, 3, argList, true); + if (!OidIsValid(procOid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("function %s does not exist", + func_signature_string(procname, 1, NIL, argList)))); } - if (OidIsValid(procOid)) - { - /* Found, but must complain and fix the pg_proc entry */ - ereport(WARNING, - (errmsg("changing argument type of function %s from \"opaque\" to \"cstring\"", - NameListToString(procname)))); - SetFunctionArgType(procOid, 0, CSTRINGOID); - - /* - * Need CommandCounterIncrement since DefineType will likely try to - * alter the pg_proc tuple again. - */ - CommandCounterIncrement(); - - return procOid; - } - - /* Use CSTRING (preferred) in the error message */ - argList[0] = CSTRINGOID; - - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_FUNCTION), - errmsg("function %s does not exist", - func_signature_string(procname, 1, NIL, argList)))); + if (get_func_rettype(procOid) != typeOid) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("type input function %s must return type %s", + NameListToString(procname), format_type_be(typeOid)))); - return InvalidOid; /* keep compiler quiet */ + return procOid; } static Oid @@ -1761,48 +1674,25 @@ findTypeOutputFunction(List *procname, Oid typeOid) Oid procOid; /* - * Output functions can take a single argument of the type. - * - * For backwards compatibility we allow OPAQUE in place of the actual type - * name; if we see this, we issue a warning and fix up the pg_proc entry. + * Output functions always take a single argument of the type and return + * cstring. */ argList[0] = typeOid; procOid = LookupFuncName(procname, 1, argList, true); - if (OidIsValid(procOid)) - return procOid; - - /* No luck, try it with OPAQUE */ - argList[0] = OPAQUEOID; - - procOid = LookupFuncName(procname, 1, argList, true); - - if (OidIsValid(procOid)) - { - /* Found, but must complain and fix the pg_proc entry */ - ereport(WARNING, - (errmsg("changing argument type of function %s from \"opaque\" to %s", - NameListToString(procname), format_type_be(typeOid)))); - SetFunctionArgType(procOid, 0, typeOid); - - /* - * Need CommandCounterIncrement since DefineType will likely try to - * alter the pg_proc tuple again. - */ - CommandCounterIncrement(); - - return procOid; - } - - /* Use type name, not OPAQUE, in the failure message. */ - argList[0] = typeOid; + if (!OidIsValid(procOid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("function %s does not exist", + func_signature_string(procname, 1, NIL, argList)))); - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_FUNCTION), - errmsg("function %s does not exist", - func_signature_string(procname, 1, NIL, argList)))); + if (get_func_rettype(procOid) != CSTRINGOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("type output function %s must return type %s", + NameListToString(procname), "cstring"))); - return InvalidOid; /* keep compiler quiet */ + return procOid; } static Oid @@ -1813,27 +1703,32 @@ findTypeReceiveFunction(List *procname, Oid typeOid) /* * Receive functions can take a single argument of type INTERNAL, or three - * arguments (internal, typioparam OID, typmod). + * arguments (internal, typioparam OID, typmod). They must return the + * target type. */ argList[0] = INTERNALOID; procOid = LookupFuncName(procname, 1, argList, true); - if (OidIsValid(procOid)) - return procOid; - - argList[1] = OIDOID; - argList[2] = INT4OID; + if (!OidIsValid(procOid)) + { + argList[1] = OIDOID; + argList[2] = INT4OID; - procOid = LookupFuncName(procname, 3, argList, true); - if (OidIsValid(procOid)) - return procOid; + procOid = LookupFuncName(procname, 3, argList, true); + if (!OidIsValid(procOid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("function %s does not exist", + func_signature_string(procname, 1, NIL, argList)))); + } - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_FUNCTION), - errmsg("function %s does not exist", - func_signature_string(procname, 1, NIL, argList)))); + if (get_func_rettype(procOid) != typeOid) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("type receive function %s must return type %s", + NameListToString(procname), format_type_be(typeOid)))); - return InvalidOid; /* keep compiler quiet */ + return procOid; } static Oid @@ -1843,20 +1738,25 @@ findTypeSendFunction(List *procname, Oid typeOid) Oid procOid; /* - * Send functions can take a single argument of the type. + * Send functions always take a single argument of the type and return + * bytea. */ argList[0] = typeOid; procOid = LookupFuncName(procname, 1, argList, true); - if (OidIsValid(procOid)) - return procOid; + if (!OidIsValid(procOid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("function %s does not exist", + func_signature_string(procname, 1, NIL, argList)))); - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_FUNCTION), - errmsg("function %s does not exist", - func_signature_string(procname, 1, NIL, argList)))); + if (get_func_rettype(procOid) != BYTEAOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("type send function %s must return type %s", + NameListToString(procname), "bytea"))); - return InvalidOid; /* keep compiler quiet */ + return procOid; } static Oid diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dede9d7..0992c23 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -55,7 +55,6 @@ extern Oid ResolveOpClass(List *opclass, Oid attrType, extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt); extern void RemoveFunctionById(Oid funcOid); extern void SetFunctionReturnType(Oid funcOid, Oid newRetType); -extern void SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType); extern ObjectAddress AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt); extern ObjectAddress CreateCast(CreateCastStmt *stmt); extern void DropCastById(Oid castOid); diff --git a/src/test/regress/expected/create_type.out b/src/test/regress/expected/create_type.out index 8309756..eb55e25 100644 --- a/src/test/regress/expected/create_type.out +++ b/src/test/regress/expected/create_type.out @@ -83,8 +83,10 @@ SELECT * FROM default_test; zippo | 42 (1 row) +-- We need a shell type to test some CREATE TYPE failure cases with +CREATE TYPE bogus_type; -- invalid: non-lowercase quoted identifiers -CREATE TYPE case_int42 ( +CREATE TYPE bogus_type ( "Internallength" = 4, "Input" = int42_in, "Output" = int42_out, @@ -111,6 +113,20 @@ WARNING: type attribute "Passedbyvalue" not recognized LINE 7: "Passedbyvalue" ^ ERROR: type input function must be specified +-- invalid: input/output function incompatibility +CREATE TYPE bogus_type (INPUT = array_in, + OUTPUT = array_out, + ELEMENT = int, + INTERNALLENGTH = 32); +ERROR: type input function array_in must return type bogus_type +DROP TYPE bogus_type; +-- It no longer is possible to issue CREATE TYPE without making a shell first +CREATE TYPE bogus_type (INPUT = array_in, + OUTPUT = array_out, + ELEMENT = int, + INTERNALLENGTH = 32); +ERROR: type "bogus_type" does not exist +HINT: Create the type as a shell type, then create its I/O functions, then do a full CREATE TYPE. -- Test stand-alone composite type CREATE TYPE default_test_row AS (f1 text_w_default, f2 int42); CREATE FUNCTION get_default_test() RETURNS SETOF default_test_row AS ' @@ -137,28 +153,25 @@ ERROR: type "text_w_default" already exists DROP TYPE default_test_row CASCADE; NOTICE: drop cascades to function get_default_test() DROP TABLE default_test; --- Check type create with input/output incompatibility -CREATE TYPE not_existing_type (INPUT = array_in, - OUTPUT = array_out, - ELEMENT = int, - INTERNALLENGTH = 32); -ERROR: function array_out(not_existing_type) does not exist --- Check dependency transfer of opaque functions when creating a new type -CREATE FUNCTION base_fn_in(cstring) RETURNS opaque AS 'boolin' +-- Check dependencies are established when creating a new type +CREATE TYPE base_type; +CREATE FUNCTION base_fn_in(cstring) RETURNS base_type AS 'boolin' LANGUAGE internal IMMUTABLE STRICT; -CREATE FUNCTION base_fn_out(opaque) RETURNS opaque AS 'boolout' +NOTICE: return type base_type is only a shell +CREATE FUNCTION base_fn_out(base_type) RETURNS cstring AS 'boolout' LANGUAGE internal IMMUTABLE STRICT; +NOTICE: argument type base_type is only a shell CREATE TYPE base_type(INPUT = base_fn_in, OUTPUT = base_fn_out); -WARNING: changing argument type of function base_fn_out from "opaque" to base_type -WARNING: changing return type of function base_fn_in from opaque to base_type -WARNING: changing return type of function base_fn_out from opaque to cstring DROP FUNCTION base_fn_in(cstring); -- error ERROR: cannot drop function base_fn_in(cstring) because other objects depend on it DETAIL: type base_type depends on function base_fn_in(cstring) function base_fn_out(base_type) depends on type base_type HINT: Use DROP ... CASCADE to drop the dependent objects too. -DROP FUNCTION base_fn_out(opaque); -- error -ERROR: function base_fn_out(opaque) does not exist +DROP FUNCTION base_fn_out(base_type); -- error +ERROR: cannot drop function base_fn_out(base_type) because other objects depend on it +DETAIL: type base_type depends on function base_fn_out(base_type) +function base_fn_in(cstring) depends on type base_type +HINT: Use DROP ... CASCADE to drop the dependent objects too. DROP TYPE base_type; -- error ERROR: cannot drop type base_type because other objects depend on it DETAIL: function base_fn_in(cstring) depends on type base_type diff --git a/src/test/regress/sql/create_type.sql b/src/test/regress/sql/create_type.sql index 3d1deba..68b04fd 100644 --- a/src/test/regress/sql/create_type.sql +++ b/src/test/regress/sql/create_type.sql @@ -84,8 +84,11 @@ INSERT INTO default_test DEFAULT VALUES; SELECT * FROM default_test; +-- We need a shell type to test some CREATE TYPE failure cases with +CREATE TYPE bogus_type; + -- invalid: non-lowercase quoted identifiers -CREATE TYPE case_int42 ( +CREATE TYPE bogus_type ( "Internallength" = 4, "Input" = int42_in, "Output" = int42_out, @@ -94,6 +97,20 @@ CREATE TYPE case_int42 ( "Passedbyvalue" ); +-- invalid: input/output function incompatibility +CREATE TYPE bogus_type (INPUT = array_in, + OUTPUT = array_out, + ELEMENT = int, + INTERNALLENGTH = 32); + +DROP TYPE bogus_type; + +-- It no longer is possible to issue CREATE TYPE without making a shell first +CREATE TYPE bogus_type (INPUT = array_in, + OUTPUT = array_out, + ELEMENT = int, + INTERNALLENGTH = 32); + -- Test stand-alone composite type CREATE TYPE default_test_row AS (f1 text_w_default, f2 int42); @@ -119,20 +136,15 @@ DROP TYPE default_test_row CASCADE; DROP TABLE default_test; --- Check type create with input/output incompatibility -CREATE TYPE not_existing_type (INPUT = array_in, - OUTPUT = array_out, - ELEMENT = int, - INTERNALLENGTH = 32); - --- Check dependency transfer of opaque functions when creating a new type -CREATE FUNCTION base_fn_in(cstring) RETURNS opaque AS 'boolin' +-- Check dependencies are established when creating a new type +CREATE TYPE base_type; +CREATE FUNCTION base_fn_in(cstring) RETURNS base_type AS 'boolin' LANGUAGE internal IMMUTABLE STRICT; -CREATE FUNCTION base_fn_out(opaque) RETURNS opaque AS 'boolout' +CREATE FUNCTION base_fn_out(base_type) RETURNS cstring AS 'boolout' LANGUAGE internal IMMUTABLE STRICT; CREATE TYPE base_type(INPUT = base_fn_in, OUTPUT = base_fn_out); DROP FUNCTION base_fn_in(cstring); -- error -DROP FUNCTION base_fn_out(opaque); -- error +DROP FUNCTION base_fn_out(base_type); -- error DROP TYPE base_type; -- error DROP TYPE base_type CASCADE; diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml index d1d0331..410eaed 100644 --- a/doc/src/sgml/datatype.sgml +++ b/doc/src/sgml/datatype.sgml @@ -4827,10 +4827,6 @@ SELECT * FROM pg_attribute <primary>unknown</primary> </indexterm> - <indexterm zone="datatype-pseudo"> - <primary>opaque</primary> - </indexterm> - <para> The <productname>PostgreSQL</productname> type system contains a number of special-purpose entries that are collectively called @@ -4953,12 +4949,6 @@ SELECT * FROM pg_attribute <entry>Identifies a not-yet-resolved type, e.g. of an undecorated string literal.</entry> </row> - - <row> - <entry><type>opaque</type></entry> - <entry>An obsolete type name that formerly served many of the above - purposes.</entry> - </row> </tbody> </tgroup> </table> diff --git a/doc/src/sgml/ref/create_language.sgml b/doc/src/sgml/ref/create_language.sgml index af9d115..2243ee6 100644 --- a/doc/src/sgml/ref/create_language.sgml +++ b/doc/src/sgml/ref/create_language.sgml @@ -211,16 +211,6 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa database, which will cause it to be available automatically in all subsequently-created databases. </para> - - <para> - In <productname>PostgreSQL</productname> versions before 7.3, it was - necessary to declare handler functions as returning the placeholder - type <type>opaque</type>, rather than <type>language_handler</type>. - To support loading - of old dump files, <command>CREATE LANGUAGE</command> will accept a function - declared as returning <type>opaque</type>, but it will issue a notice and - change the function's declared return type to <type>language_handler</type>. - </para> </refsect1> <refsect1 id="sql-createlanguage-examples"> diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml index 3339a4b..3b8f25e 100644 --- a/doc/src/sgml/ref/create_trigger.sgml +++ b/doc/src/sgml/ref/create_trigger.sgml @@ -543,15 +543,6 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</ row-level triggers with transition relations cannot be defined on partitions or inheritance child tables. </para> - - <para> - In <productname>PostgreSQL</productname> versions before 7.3, it was - necessary to declare trigger functions as returning the placeholder - type <type>opaque</type>, rather than <type>trigger</type>. To support loading - of old dump files, <command>CREATE TRIGGER</command> will accept a function - declared as returning <type>opaque</type>, but it will issue a notice and - change the function's declared return type to <type>trigger</type>. - </para> </refsect1> <refsect1 id="sql-createtrigger-examples"> diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index 9279c05..e634ccf 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -1399,49 +1399,6 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt) return address; } -/* - * SetFunctionReturnType - change declared return type of a function - * - * This is presently only used for adjusting legacy functions that return - * OPAQUE to return whatever we find their correct definition should be. - * The caller should emit a suitable warning explaining what we did. - */ -void -SetFunctionReturnType(Oid funcOid, Oid newRetType) -{ - Relation pg_proc_rel; - HeapTuple tup; - Form_pg_proc procForm; - ObjectAddress func_address; - ObjectAddress type_address; - - pg_proc_rel = table_open(ProcedureRelationId, RowExclusiveLock); - - tup = SearchSysCacheCopy1(PROCOID, ObjectIdGetDatum(funcOid)); - if (!HeapTupleIsValid(tup)) /* should not happen */ - elog(ERROR, "cache lookup failed for function %u", funcOid); - procForm = (Form_pg_proc) GETSTRUCT(tup); - - if (procForm->prorettype != OPAQUEOID) /* caller messed up */ - elog(ERROR, "function %u doesn't return OPAQUE", funcOid); - - /* okay to overwrite copied tuple */ - procForm->prorettype = newRetType; - - /* update the catalog and its indexes */ - CatalogTupleUpdate(pg_proc_rel, &tup->t_self, tup); - - table_close(pg_proc_rel, RowExclusiveLock); - - /* - * Also update the dependency to the new type. Opaque is a pinned type, so - * there is no old dependency record for it that we would need to remove. - */ - ObjectAddressSet(type_address, TypeRelationId, newRetType); - ObjectAddressSet(func_address, ProcedureRelationId, funcOid); - recordDependencyOn(&func_address, &type_address, DEPENDENCY_NORMAL); -} - /* * CREATE CAST diff --git a/src/backend/commands/proclang.c b/src/backend/commands/proclang.c index 9d72edb..21ceeb7 100644 --- a/src/backend/commands/proclang.c +++ b/src/backend/commands/proclang.c @@ -74,27 +74,10 @@ CreateProceduralLanguage(CreatePLangStmt *stmt) handlerOid = LookupFuncName(stmt->plhandler, 0, NULL, false); funcrettype = get_func_rettype(handlerOid); if (funcrettype != LANGUAGE_HANDLEROID) - { - /* - * We allow OPAQUE just so we can load old dump files. When we see a - * handler function declared OPAQUE, change it to LANGUAGE_HANDLER. - * (This is probably obsolete and removable?) - */ - if (funcrettype == OPAQUEOID) - { - ereport(WARNING, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("changing return type of function %s from %s to %s", - NameListToString(stmt->plhandler), - "opaque", "language_handler"))); - SetFunctionReturnType(handlerOid, LANGUAGE_HANDLEROID); - } - else - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("function %s must return type %s", - NameListToString(stmt->plhandler), "language_handler"))); - } + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("function %s must return type %s", + NameListToString(stmt->plhandler), "language_handler"))); /* validate the inline function */ if (stmt->plinline) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 6e8b722..056a912 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -699,25 +699,10 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, } funcrettype = get_func_rettype(funcoid); if (funcrettype != TRIGGEROID) - { - /* - * We allow OPAQUE just so we can load old dump files. When we see a - * trigger function declared OPAQUE, change it to TRIGGER. - */ - if (funcrettype == OPAQUEOID) - { - ereport(WARNING, - (errmsg("changing return type of function %s from %s to %s", - NameListToString(stmt->funcname), - "opaque", "trigger"))); - SetFunctionReturnType(funcoid, TRIGGEROID); - } - else - ereport(ERROR, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("function %s must return type %s", - NameListToString(stmt->funcname), "trigger"))); - } + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("function %s must return type %s", + NameListToString(stmt->funcname), "trigger"))); /* * If the command is a user-entered CREATE CONSTRAINT TRIGGER command that diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c index ad0e363..4653fc3 100644 --- a/src/backend/utils/adt/pseudotypes.c +++ b/src/backend/utils/adt/pseudotypes.c @@ -415,7 +415,6 @@ PSEUDOTYPE_DUMMY_IO_FUNCS(fdw_handler); PSEUDOTYPE_DUMMY_IO_FUNCS(index_am_handler); PSEUDOTYPE_DUMMY_IO_FUNCS(tsm_handler); PSEUDOTYPE_DUMMY_IO_FUNCS(internal); -PSEUDOTYPE_DUMMY_IO_FUNCS(opaque); PSEUDOTYPE_DUMMY_IO_FUNCS(anyelement); PSEUDOTYPE_DUMMY_IO_FUNCS(anynonarray); PSEUDOTYPE_DUMMY_IO_FUNCS(table_am_handler); diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index ef15390..d3ab5bb 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -82,10 +82,9 @@ typedef struct typedef enum OidOptions { - zeroAsOpaque = 1, - zeroAsAny = 2, - zeroAsStar = 4, - zeroAsNone = 8 + zeroIsError = 1, + zeroAsStar = 2, + zeroAsNone = 4 } OidOptions; /* global decls */ @@ -122,8 +121,6 @@ static SimpleStringList tabledata_exclude_patterns = {NULL, NULL}; static SimpleOidList tabledata_exclude_oids = {NULL, NULL}; -char g_opaque_type[10]; /* name for the opaque type */ - /* placeholders for the delimiters for comments */ char g_comment_start[10]; char g_comment_end[10]; @@ -404,7 +401,6 @@ main(int argc, char **argv) strcpy(g_comment_start, "-- "); g_comment_end[0] = '\0'; - strcpy(g_opaque_type, "opaque"); progname = get_progname(argv[0]); @@ -10736,7 +10732,7 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo) { char *elemType; - elemType = getFormattedTypeName(fout, tyinfo->typelem, zeroAsOpaque); + elemType = getFormattedTypeName(fout, tyinfo->typelem, zeroIsError); appendPQExpBuffer(q, ",\n ELEMENT = %s", elemType); free(elemType); } @@ -11547,7 +11543,7 @@ format_function_arguments_old(Archive *fout, const char *argname; typid = allargtypes ? atooid(allargtypes[j]) : finfo->argtypes[j]; - typname = getFormattedTypeName(fout, typid, zeroAsOpaque); + typname = getFormattedTypeName(fout, typid, zeroIsError); if (argmodes) { @@ -11616,7 +11612,7 @@ format_function_signature(Archive *fout, FuncInfo *finfo, bool honor_quotes) appendPQExpBufferStr(&fn, ", "); typname = getFormattedTypeName(fout, finfo->argtypes[j], - zeroAsOpaque); + zeroIsError); appendPQExpBufferStr(&fn, typname); free(typname); } @@ -12021,7 +12017,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo) else { rettypename = getFormattedTypeName(fout, finfo->prorettype, - zeroAsOpaque); + zeroIsError); appendPQExpBuffer(q, " RETURNS %s%s", (proretset[0] == 't') ? "SETOF " : "", rettypename); @@ -13740,7 +13736,7 @@ format_aggregate_signature(AggInfo *agginfo, Archive *fout, bool honor_quotes) char *typname; typname = getFormattedTypeName(fout, agginfo->aggfn.argtypes[j], - zeroAsOpaque); + zeroIsError); appendPQExpBuffer(&buf, "%s%s", (j > 0) ? ", " : "", @@ -18363,11 +18359,7 @@ getFormattedTypeName(Archive *fout, Oid oid, OidOptions opts) if (oid == 0) { - if ((opts & zeroAsOpaque) != 0) - return pg_strdup(g_opaque_type); - else if ((opts & zeroAsAny) != 0) - return pg_strdup("'any'"); - else if ((opts & zeroAsStar) != 0) + if ((opts & zeroAsStar) != 0) return pg_strdup("*"); else if ((opts & zeroAsNone) != 0) return pg_strdup("NONE"); diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 21004e5..852020b 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -640,8 +640,6 @@ typedef struct _extensionMemberId extern char g_comment_start[10]; extern char g_comment_end[10]; -extern char g_opaque_type[10]; /* name for the opaque type */ - /* * common utility functions */ diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 07a86c7..7fb574f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -7054,12 +7054,6 @@ { oid => '2305', descr => 'I/O', proname => 'internal_out', prorettype => 'cstring', proargtypes => 'internal', prosrc => 'internal_out' }, -{ oid => '2306', descr => 'I/O', - proname => 'opaque_in', proisstrict => 'f', prorettype => 'opaque', - proargtypes => 'cstring', prosrc => 'opaque_in' }, -{ oid => '2307', descr => 'I/O', - proname => 'opaque_out', prorettype => 'cstring', proargtypes => 'opaque', - prosrc => 'opaque_out' }, { oid => '2312', descr => 'I/O', proname => 'anyelement_in', prorettype => 'anyelement', proargtypes => 'cstring', prosrc => 'anyelement_in' }, @@ -7067,10 +7061,10 @@ proname => 'anyelement_out', prorettype => 'cstring', proargtypes => 'anyelement', prosrc => 'anyelement_out' }, { oid => '2398', descr => 'I/O', - proname => 'shell_in', proisstrict => 'f', prorettype => 'opaque', + proname => 'shell_in', proisstrict => 'f', prorettype => 'void', proargtypes => 'cstring', prosrc => 'shell_in' }, { oid => '2399', descr => 'I/O', - proname => 'shell_out', prorettype => 'cstring', proargtypes => 'opaque', + proname => 'shell_out', prorettype => 'cstring', proargtypes => 'void', prosrc => 'shell_out' }, { oid => '2597', descr => 'I/O', proname => 'domain_in', proisstrict => 'f', provolatile => 's', diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat index 4cf2b9d..b00597d 100644 --- a/src/include/catalog/pg_type.dat +++ b/src/include/catalog/pg_type.dat @@ -546,10 +546,6 @@ typtype => 'p', typcategory => 'P', typinput => 'internal_in', typoutput => 'internal_out', typreceive => '-', typsend => '-', typalign => 'ALIGNOF_POINTER' }, -{ oid => '2282', descr => 'obsolete, deprecated pseudo-type', - typname => 'opaque', typlen => '4', typbyval => 't', typtype => 'p', - typcategory => 'P', typinput => 'opaque_in', typoutput => 'opaque_out', - typreceive => '-', typsend => '-', typalign => 'i' }, { oid => '2283', descr => 'pseudo-type representing a polymorphic base type', typname => 'anyelement', typlen => '4', typbyval => 't', typtype => 'p', typcategory => 'P', typinput => 'anyelement_in', diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 0992c23..5cd6975 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -54,7 +54,6 @@ extern Oid ResolveOpClass(List *opclass, Oid attrType, /* commands/functioncmds.c */ extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt); extern void RemoveFunctionById(Oid funcOid); -extern void SetFunctionReturnType(Oid funcOid, Oid newRetType); extern ObjectAddress AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt); extern ObjectAddress CreateCast(CreateCastStmt *stmt); extern void DropCastById(Oid castOid); diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c index a65bce0..5fdf303 100644 --- a/src/pl/plperl/plperl.c +++ b/src/pl/plperl/plperl.c @@ -2000,9 +2000,7 @@ plperl_validator(PG_FUNCTION_ARGS) /* except for TRIGGER, EVTTRIGGER, RECORD, or VOID */ if (functyptype == TYPTYPE_PSEUDO) { - /* we assume OPAQUE with no arguments means a trigger */ - if (proc->prorettype == TRIGGEROID || - (proc->prorettype == OPAQUEOID && proc->pronargs == 0)) + if (proc->prorettype == TRIGGEROID) is_trigger = true; else if (proc->prorettype == EVTTRIGGEROID) is_event_trigger = true; diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c index b83087e..b434818 100644 --- a/src/pl/plpgsql/src/pl_handler.c +++ b/src/pl/plpgsql/src/pl_handler.c @@ -421,12 +421,10 @@ plpgsql_validator(PG_FUNCTION_ARGS) functyptype = get_typtype(proc->prorettype); /* Disallow pseudotype result */ - /* except for TRIGGER, RECORD, VOID, or polymorphic */ + /* except for TRIGGER, EVTTRIGGER, RECORD, VOID, or polymorphic */ if (functyptype == TYPTYPE_PSEUDO) { - /* we assume OPAQUE with no arguments means a trigger */ - if (proc->prorettype == TRIGGEROID || - (proc->prorettype == OPAQUEOID && proc->pronargs == 0)) + if (proc->prorettype == TRIGGEROID) is_dml_trigger = true; else if (proc->prorettype == EVTTRIGGEROID) is_event_trigger = true; diff --git a/src/pl/plpython/plpy_main.c b/src/pl/plpython/plpy_main.c index 882d69e..3eedaa8 100644 --- a/src/pl/plpython/plpy_main.c +++ b/src/pl/plpython/plpy_main.c @@ -379,9 +379,7 @@ plpython2_inline_handler(PG_FUNCTION_ARGS) static bool PLy_procedure_is_trigger(Form_pg_proc procStruct) { - return (procStruct->prorettype == TRIGGEROID || - (procStruct->prorettype == OPAQUEOID && - procStruct->pronargs == 0)); + return (procStruct->prorettype == TRIGGEROID); } static void diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index fb6c029..40468e8 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -384,7 +384,7 @@ FROM pg_proc as p1 WHERE p1.prorettype = 'cstring'::regtype AND NOT EXISTS(SELECT 1 FROM pg_type WHERE typoutput = p1.oid) AND NOT EXISTS(SELECT 1 FROM pg_type WHERE typmodout = p1.oid) - AND p1.oid != 'shell_out(opaque)'::regprocedure + AND p1.oid != 'shell_out(void)'::regprocedure ORDER BY 1; oid | proname ------+-------------- diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql index 8351b64..f06f245 100644 --- a/src/test/regress/sql/opr_sanity.sql +++ b/src/test/regress/sql/opr_sanity.sql @@ -309,7 +309,7 @@ FROM pg_proc as p1 WHERE p1.prorettype = 'cstring'::regtype AND NOT EXISTS(SELECT 1 FROM pg_type WHERE typoutput = p1.oid) AND NOT EXISTS(SELECT 1 FROM pg_type WHERE typmodout = p1.oid) - AND p1.oid != 'shell_out(opaque)'::regprocedure + AND p1.oid != 'shell_out(void)'::regprocedure ORDER BY 1; -- Check for length inconsistencies between the various argument-info arrays. diff --git a/doc/src/sgml/ref/alter_type.sgml b/doc/src/sgml/ref/alter_type.sgml index 67be1dd..2543b20 100644 --- a/doc/src/sgml/ref/alter_type.sgml +++ b/doc/src/sgml/ref/alter_type.sgml @@ -30,6 +30,7 @@ ALTER TYPE <replaceable class="parameter">name</replaceable> RENAME TO <replacea ALTER TYPE <replaceable class="parameter">name</replaceable> SET SCHEMA <replaceable class="parameter">new_schema</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> @@ -70,7 +71,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. @@ -135,6 +136,82 @@ 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. + </para> + </listitem> + </varlistentry> </variablelist> </para> @@ -156,7 +233,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> @@ -353,7 +430,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> diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c index 56e0bcf..cd56714 100644 --- a/src/backend/catalog/pg_type.c +++ b/src/backend/catalog/pg_type.c @@ -155,8 +155,8 @@ TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId) * Create dependencies. We can/must skip this in bootstrap mode. */ if (!IsBootstrapProcessingMode()) - GenerateTypeDependencies(typoid, - (Form_pg_type) GETSTRUCT(tup), + GenerateTypeDependencies(tup, + pg_type_desc, NULL, NULL, 0, @@ -488,8 +488,8 @@ TypeCreate(Oid newTypeOid, * Create dependencies. We can/must skip this in bootstrap mode. */ if (!IsBootstrapProcessingMode()) - GenerateTypeDependencies(typeObjectId, - (Form_pg_type) GETSTRUCT(tup), + GenerateTypeDependencies(tup, + pg_type_desc, (defaultTypeBin ? stringToNode(defaultTypeBin) : NULL), @@ -516,12 +516,16 @@ TypeCreate(Oid newTypeOid, * GenerateTypeDependencies: build the dependencies needed for a type * * Most of what this function needs to know about the type is passed as the - * new pg_type row, typeForm. But we can't get at the varlena fields through - * that, so defaultExpr and typacl are passed separately. (typacl is really + * new pg_type row, typeTuple. We make callers pass the pg_type Relation + * as well, so that we have easy access to a tuple descriptor for the row. + * + * While this is able to extract the defaultExpr and typacl from the tuple, + * doing so is relatively expensive, and callers may have those values at + * hand already. Pass those if handy, otherwise pass NULL. (typacl is really * "Acl *", but we declare it "void *" to avoid including acl.h in pg_type.h.) * - * relationKind and isImplicitArray aren't visible in the pg_type row either, - * so they're also passed separately. + * relationKind and isImplicitArray are likewise somewhat expensive to deduce + * from the tuple, so we make callers pass those (they're not optional). * * isDependentType is true if this is an implicit array or relation rowtype; * that means it doesn't need its own dependencies on owner etc. @@ -535,8 +539,8 @@ TypeCreate(Oid newTypeOid, * that type will become a member of the extension.) */ void -GenerateTypeDependencies(Oid typeObjectId, - Form_pg_type typeForm, +GenerateTypeDependencies(HeapTuple typeTuple, + Relation typeCatalog, Node *defaultExpr, void *typacl, char relationKind, /* only for relation rowtypes */ @@ -544,9 +548,30 @@ GenerateTypeDependencies(Oid typeObjectId, bool isDependentType, bool rebuild) { + Form_pg_type typeForm = (Form_pg_type) GETSTRUCT(typeTuple); + Oid typeObjectId = typeForm->oid; + Datum datum; + bool isNull; ObjectAddress myself, referenced; + /* Extract defaultExpr if caller didn't pass it */ + if (defaultExpr == NULL) + { + datum = heap_getattr(typeTuple, Anum_pg_type_typdefaultbin, + RelationGetDescr(typeCatalog), &isNull); + if (!isNull) + defaultExpr = stringToNode(TextDatumGetCString(datum)); + } + /* Extract typacl if caller didn't pass it */ + if (typacl == NULL) + { + datum = heap_getattr(typeTuple, Anum_pg_type_typacl, + RelationGetDescr(typeCatalog), &isNull); + if (!isNull) + typacl = DatumGetAclPCopy(datum); + } + /* If rebuild, first flush old dependencies, except extension deps */ if (rebuild) { diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index d732a3a..8348d79 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -83,6 +83,25 @@ typedef struct /* atts[] is of allocated length RelationGetNumberOfAttributes(rel) */ } RelToCheck; +/* parameter structure for AlterTypeRecurse() */ +typedef struct +{ + /* Flags indicating which type attributes to update */ + bool updateStorage; + bool updateReceive; + bool updateSend; + bool updateTypmodin; + bool updateTypmodout; + bool updateAnalyze; + /* New values for relevant attributes */ + char storage; + Oid receiveOid; + Oid sendOid; + Oid typmodinOid; + Oid typmodoutOid; + Oid analyzeOid; +} AlterTypeRecurseParams; + /* Potentially set by pg_upgrade_support functions */ Oid binary_upgrade_next_array_pg_type_oid = InvalidOid; @@ -107,6 +126,8 @@ static char *domainAddConstraint(Oid domainOid, Oid domainNamespace, const char *domainName, ObjectAddress *constrAddr); static Node *replace_domain_constraint_value(ParseState *pstate, ColumnRef *cref); +static void AlterTypeRecurse(Oid typeOid, HeapTuple tup, Relation catalog, + AlterTypeRecurseParams *atparams); /* @@ -466,9 +487,12 @@ DefineType(ParseState *pstate, List *names, List *parameters) * minimum sane check would be for execute-with-grant-option. But we * don't have a way to make the type go away if the grant option is * revoked, so ownership seems better. + * + * XXX For now, this is all unnecessary given the superuser check above. + * If we ever relax that, these calls likely should be moved into + * findTypeInputFunction et al, where they could be shared by AlterType. */ #ifdef NOT_USED - /* XXX this is unnecessary given the superuser check above */ if (inputOid && !pg_proc_ownercheck(inputOid, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION, NameListToString(inputName)); @@ -493,47 +517,6 @@ DefineType(ParseState *pstate, List *names, List *parameters) #endif /* - * Print warnings if any of the type's I/O functions are marked volatile. - * There is a general assumption that I/O functions are stable or - * immutable; this allows us for example to mark record_in/record_out - * stable rather than volatile. Ideally we would throw errors not just - * warnings here; but since this check is new as of 9.5, and since the - * volatility marking might be just an error-of-omission and not a true - * indication of how the function behaves, we'll let it pass as a warning - * for now. - */ - if (inputOid && func_volatile(inputOid) == PROVOLATILE_VOLATILE) - ereport(WARNING, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("type input function %s should not be volatile", - NameListToString(inputName)))); - if (outputOid && func_volatile(outputOid) == PROVOLATILE_VOLATILE) - ereport(WARNING, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("type output function %s should not be volatile", - NameListToString(outputName)))); - if (receiveOid && func_volatile(receiveOid) == PROVOLATILE_VOLATILE) - ereport(WARNING, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("type receive function %s should not be volatile", - NameListToString(receiveName)))); - if (sendOid && func_volatile(sendOid) == PROVOLATILE_VOLATILE) - ereport(WARNING, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("type send function %s should not be volatile", - NameListToString(sendName)))); - if (typmodinOid && func_volatile(typmodinOid) == PROVOLATILE_VOLATILE) - ereport(WARNING, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("type modifier input function %s should not be volatile", - NameListToString(typmodinName)))); - if (typmodoutOid && func_volatile(typmodoutOid) == PROVOLATILE_VOLATILE) - ereport(WARNING, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("type modifier output function %s should not be volatile", - NameListToString(typmodoutName)))); - - /* * OK, we're done checking, time to make the type. We must assign the * array type OID ahead of calling TypeCreate, since the base type and * array type each refer to the other. @@ -765,6 +748,12 @@ DefineDomain(CreateDomainStmt *stmt) aclcheck_error_type(aclresult, basetypeoid); /* + * Collect the properties of the new domain. Some are inherited from the + * base type, some are not. If you change any of this inheritance + * behavior, be sure to update AlterTypeRecurse() to match! + */ + + /* * Identify the collation if any */ baseColl = baseType->typcollation; @@ -1664,6 +1653,22 @@ findTypeInputFunction(List *procname, Oid typeOid) errmsg("type input function %s must return type %s", NameListToString(procname), format_type_be(typeOid)))); + /* + * Print warnings if any of the type's I/O functions are marked volatile. + * There is a general assumption that I/O functions are stable or + * immutable; this allows us for example to mark record_in/record_out + * stable rather than volatile. Ideally we would throw errors not just + * warnings here; but since this check is new as of 9.5, and since the + * volatility marking might be just an error-of-omission and not a true + * indication of how the function behaves, we'll let it pass as a warning + * for now. + */ + if (func_volatile(procOid) == PROVOLATILE_VOLATILE) + ereport(WARNING, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("type input function %s should not be volatile", + NameListToString(procname)))); + return procOid; } @@ -1692,6 +1697,13 @@ findTypeOutputFunction(List *procname, Oid typeOid) errmsg("type output function %s must return type %s", NameListToString(procname), "cstring"))); + /* Just a warning for now, per comments in findTypeInputFunction */ + if (func_volatile(procOid) == PROVOLATILE_VOLATILE) + ereport(WARNING, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("type output function %s should not be volatile", + NameListToString(procname)))); + return procOid; } @@ -1728,6 +1740,13 @@ findTypeReceiveFunction(List *procname, Oid typeOid) errmsg("type receive function %s must return type %s", NameListToString(procname), format_type_be(typeOid)))); + /* Just a warning for now, per comments in findTypeInputFunction */ + if (func_volatile(procOid) == PROVOLATILE_VOLATILE) + ereport(WARNING, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("type receive function %s should not be volatile", + NameListToString(procname)))); + return procOid; } @@ -1756,6 +1775,13 @@ findTypeSendFunction(List *procname, Oid typeOid) errmsg("type send function %s must return type %s", NameListToString(procname), "bytea"))); + /* Just a warning for now, per comments in findTypeInputFunction */ + if (func_volatile(procOid) == PROVOLATILE_VOLATILE) + ereport(WARNING, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("type send function %s should not be volatile", + NameListToString(procname)))); + return procOid; } @@ -1783,6 +1809,13 @@ findTypeTypmodinFunction(List *procname) errmsg("typmod_in function %s must return type %s", NameListToString(procname), "integer"))); + /* Just a warning for now, per comments in findTypeInputFunction */ + if (func_volatile(procOid) == PROVOLATILE_VOLATILE) + ereport(WARNING, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("type modifier input function %s should not be volatile", + NameListToString(procname)))); + return procOid; } @@ -1810,6 +1843,13 @@ findTypeTypmodoutFunction(List *procname) errmsg("typmod_out function %s must return type %s", NameListToString(procname), "cstring"))); + /* Just a warning for now, per comments in findTypeInputFunction */ + if (func_volatile(procOid) == PROVOLATILE_VOLATILE) + ereport(WARNING, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("type modifier output function %s should not be volatile", + NameListToString(procname)))); + return procOid; } @@ -2086,9 +2126,6 @@ AlterDomainDefault(List *names, Node *defaultRaw) Relation rel; char *defaultValue; Node *defaultExpr = NULL; /* NULL if no default specified */ - Acl *typacl; - Datum aclDatum; - bool isNull; Datum new_record[Natts_pg_type]; bool new_record_nulls[Natts_pg_type]; bool new_record_repl[Natts_pg_type]; @@ -2141,6 +2178,7 @@ AlterDomainDefault(List *names, Node *defaultRaw) (IsA(defaultExpr, Const) &&((Const *) defaultExpr)->constisnull)) { /* Default is NULL, drop it */ + defaultExpr = NULL; new_record_nulls[Anum_pg_type_typdefaultbin - 1] = true; new_record_repl[Anum_pg_type_typdefaultbin - 1] = true; new_record_nulls[Anum_pg_type_typdefault - 1] = true; @@ -2181,19 +2219,11 @@ AlterDomainDefault(List *names, Node *defaultRaw) CatalogTupleUpdate(rel, &tup->t_self, newtuple); - /* Must extract ACL for use of GenerateTypeDependencies */ - aclDatum = heap_getattr(newtuple, Anum_pg_type_typacl, - RelationGetDescr(rel), &isNull); - if (isNull) - typacl = NULL; - else - typacl = DatumGetAclPCopy(aclDatum); - /* Rebuild dependencies */ - GenerateTypeDependencies(domainoid, - (Form_pg_type) GETSTRUCT(newtuple), + GenerateTypeDependencies(newtuple, + rel, defaultExpr, - typacl, + NULL, /* don't have typacl handy */ 0, /* relation kind is n/a */ false, /* a domain isn't an implicit array */ false, /* nor is it any kind of dependent type */ @@ -3609,3 +3639,338 @@ AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid, return oldNspOid; } + +/* + * AlterType + * ALTER TYPE <type> SET (option = ...) + */ +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 below. + */ + 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. + */ +static void +AlterTypeRecurse(Oid typeOid, HeapTuple tup, Relation catalog, + AlterTypeRecurseParams *atparams) +{ + Datum values[Natts_pg_type]; + bool nulls[Natts_pg_type]; + bool replaces[Natts_pg_type]; + HeapTuple newtup; + SysScanDesc scan; + ScanKeyData key[1]; + HeapTuple domainTup; + + /* Since this function recurses, it could be driven to stack overflow */ + check_stack_depth(); + + /* Update the current type's tuple */ + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + memset(replaces, 0, sizeof(replaces)); + + if (atparams->updateStorage) + { + replaces[Anum_pg_type_typstorage - 1] = true; + values[Anum_pg_type_typstorage - 1] = CharGetDatum(atparams->storage); + } + if (atparams->updateReceive) + { + replaces[Anum_pg_type_typreceive - 1] = true; + values[Anum_pg_type_typreceive - 1] = ObjectIdGetDatum(atparams->receiveOid); + } + if (atparams->updateSend) + { + replaces[Anum_pg_type_typsend - 1] = true; + values[Anum_pg_type_typsend - 1] = ObjectIdGetDatum(atparams->sendOid); + } + if (atparams->updateTypmodin) + { + replaces[Anum_pg_type_typmodin - 1] = true; + values[Anum_pg_type_typmodin - 1] = ObjectIdGetDatum(atparams->typmodinOid); + } + if (atparams->updateTypmodout) + { + replaces[Anum_pg_type_typmodout - 1] = true; + values[Anum_pg_type_typmodout - 1] = ObjectIdGetDatum(atparams->typmodoutOid); + } + if (atparams->updateAnalyze) + { + replaces[Anum_pg_type_typanalyze - 1] = true; + values[Anum_pg_type_typanalyze - 1] = ObjectIdGetDatum(atparams->analyzeOid); + } + + newtup = heap_modify_tuple(tup, RelationGetDescr(catalog), + values, nulls, replaces); + + CatalogTupleUpdate(catalog, &newtup->t_self, newtup); + + /* Rebuild dependencies for this type */ + GenerateTypeDependencies(newtup, + catalog, + NULL, /* don't have defaultExpr handy */ + NULL, /* don't have typacl handy */ + 0, /* we rejected composite types above */ + false, /* and we rejected implicit arrays above */ + false, /* so it can't be a dependent type */ + true); + + InvokeObjectPostAlterHook(TypeRelationId, typeOid, 0); + + /* + * Now we need to recurse to domains. However, some properties are not + * inherited by domains, so clear the update flags for those. + */ + atparams->updateReceive = false; /* domains use F_DOMAIN_RECV */ + atparams->updateTypmodin = false; /* domains don't have typmods */ + atparams->updateTypmodout = false; + + /* Search pg_type for possible domains over this type */ + ScanKeyInit(&key[0], + Anum_pg_type_typbasetype, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(typeOid)); + + scan = systable_beginscan(catalog, InvalidOid, false, + NULL, 1, key); + + while ((domainTup = systable_getnext(scan)) != NULL) + { + Form_pg_type domainForm = (Form_pg_type) GETSTRUCT(domainTup); + + /* + * Shouldn't have a nonzero typbasetype in a non-domain, but let's + * check + */ + if (domainForm->typtype != TYPTYPE_DOMAIN) + continue; + + AlterTypeRecurse(domainForm->oid, domainTup, catalog, atparams); + } + + systable_endscan(scan); +} diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index e04c33e..eaab97f 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3636,6 +3636,17 @@ _copyAlterOperatorStmt(const AlterOperatorStmt *from) return newnode; } +static AlterTypeStmt * +_copyAlterTypeStmt(const AlterTypeStmt *from) +{ + AlterTypeStmt *newnode = makeNode(AlterTypeStmt); + + COPY_NODE_FIELD(typeName); + COPY_NODE_FIELD(options); + + return newnode; +} + static RuleStmt * _copyRuleStmt(const RuleStmt *from) { @@ -5263,6 +5274,9 @@ copyObjectImpl(const void *from) case T_AlterOperatorStmt: retval = _copyAlterOperatorStmt(from); break; + case T_AlterTypeStmt: + retval = _copyAlterTypeStmt(from); + break; case T_RuleStmt: retval = _copyRuleStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 5b1ba14..88b9129 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1482,6 +1482,15 @@ _equalAlterOperatorStmt(const AlterOperatorStmt *a, const AlterOperatorStmt *b) } static bool +_equalAlterTypeStmt(const AlterTypeStmt *a, const AlterTypeStmt *b) +{ + COMPARE_NODE_FIELD(typeName); + COMPARE_NODE_FIELD(options); + + return true; +} + +static bool _equalRuleStmt(const RuleStmt *a, const RuleStmt *b) { COMPARE_NODE_FIELD(relation); @@ -3359,6 +3368,9 @@ equal(const void *a, const void *b) case T_AlterOperatorStmt: retval = _equalAlterOperatorStmt(a, b); break; + case T_AlterTypeStmt: + retval = _equalAlterTypeStmt(a, b); + break; case T_RuleStmt: retval = _equalRuleStmt(a, b); break; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 96e7fdb..7e384f9 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -249,7 +249,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt AlterFdwStmt AlterForeignServerStmt AlterGroupStmt AlterObjectDependsStmt AlterObjectSchemaStmt AlterOwnerStmt - AlterOperatorStmt AlterSeqStmt AlterSystemStmt AlterTableStmt + AlterOperatorStmt AlterTypeStmt AlterSeqStmt AlterSystemStmt AlterTableStmt AlterTblSpcStmt AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt AlterCompositeTypeStmt AlterUserMappingStmt AlterRoleStmt AlterRoleSetStmt AlterPolicyStmt AlterStatsStmt @@ -847,6 +847,7 @@ stmt : | AlterObjectSchemaStmt | AlterOwnerStmt | AlterOperatorStmt + | AlterTypeStmt | AlterPolicyStmt | AlterSeqStmt | AlterSystemStmt @@ -9367,6 +9368,24 @@ operator_def_arg: /***************************************************************************** * + * ALTER TYPE name SET define + * + * We repurpose ALTER OPERATOR's version of "definition" here + * + *****************************************************************************/ + +AlterTypeStmt: + ALTER TYPE_P any_name SET '(' operator_def_list ')' + { + AlterTypeStmt *n = makeNode(AlterTypeStmt); + n->typeName = $3; + n->options = $6; + $$ = (Node *)n; + } + ; + +/***************************************************************************** + * * ALTER THING name OWNER TO newname * *****************************************************************************/ diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 1b460a2..b1f7f6e 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -162,6 +162,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree) case T_AlterTableMoveAllStmt: case T_AlterTableSpaceOptionsStmt: case T_AlterTableStmt: + case T_AlterTypeStmt: case T_AlterUserMappingStmt: case T_CommentStmt: case T_CompositeTypeStmt: @@ -1713,6 +1714,10 @@ ProcessUtilitySlow(ParseState *pstate, address = AlterOperator((AlterOperatorStmt *) parsetree); break; + case T_AlterTypeStmt: + address = AlterType((AlterTypeStmt *) parsetree); + break; + case T_CommentStmt: address = CommentObject((CommentStmt *) parsetree); break; @@ -2895,6 +2900,10 @@ CreateCommandTag(Node *parsetree) tag = CMDTAG_ALTER_OPERATOR; break; + case T_AlterTypeStmt: + tag = CMDTAG_ALTER_TYPE; + break; + case T_AlterTSDictionaryStmt: tag = CMDTAG_ALTER_TEXT_SEARCH_DICTIONARY; break; @@ -3251,6 +3260,10 @@ GetCommandLogLevel(Node *parsetree) lev = LOGSTMT_DDL; break; + case T_AlterTypeStmt: + lev = LOGSTMT_DDL; + break; + case T_AlterTableMoveAllStmt: case T_AlterTableStmt: lev = LOGSTMT_DDL; diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index b6b08d0..54d0317 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -2150,7 +2150,7 @@ psql_completion(const char *text, int start, int end) else if (Matches("ALTER", "TYPE", MatchAny)) COMPLETE_WITH("ADD ATTRIBUTE", "ADD VALUE", "ALTER ATTRIBUTE", "DROP ATTRIBUTE", - "OWNER TO", "RENAME", "SET SCHEMA"); + "OWNER TO", "RENAME", "SET SCHEMA", "SET ("); /* complete ALTER TYPE <foo> ADD with actions */ else if (Matches("ALTER", "TYPE", MatchAny, "ADD")) COMPLETE_WITH("ATTRIBUTE", "VALUE"); diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h index f972f94..9789094 100644 --- a/src/include/catalog/pg_type.h +++ b/src/include/catalog/pg_type.h @@ -340,8 +340,8 @@ extern ObjectAddress TypeCreate(Oid newTypeOid, bool typeNotNull, Oid typeCollation); -extern void GenerateTypeDependencies(Oid typeObjectId, - Form_pg_type typeForm, +extern void GenerateTypeDependencies(HeapTuple typeTuple, + Relation typeCatalog, Node *defaultExpr, void *typacl, char relationKind, /* only for relation diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h index fc18d64..0162bc2 100644 --- a/src/include/commands/typecmds.h +++ b/src/include/commands/typecmds.h @@ -54,4 +54,6 @@ extern Oid AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid, bool errorOnTableType, ObjectAddresses *objsMoved); +extern ObjectAddress AlterType(AlterTypeStmt *stmt); + #endif /* TYPECMDS_H */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index baced7e..8a76afe 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -380,6 +380,7 @@ typedef enum NodeTag T_AlterObjectSchemaStmt, T_AlterOwnerStmt, T_AlterOperatorStmt, + T_AlterTypeStmt, T_DropOwnedStmt, T_ReassignOwnedStmt, T_CompositeTypeStmt, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index da0706a..2039b42 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2959,9 +2959,8 @@ typedef struct AlterOwnerStmt RoleSpec *newowner; /* the new owner */ } AlterOwnerStmt; - /* ---------------------- - * Alter Operator Set Restrict, Join + * Alter Operator Set ( this-n-that ) * ---------------------- */ typedef struct AlterOperatorStmt @@ -2971,6 +2970,16 @@ typedef struct AlterOperatorStmt List *options; /* List of DefElem nodes */ } AlterOperatorStmt; +/* ------------------------ + * Alter Type Set ( this-n-that ) + * ------------------------ + */ +typedef struct AlterTypeStmt +{ + NodeTag type; + List *typeName; /* type name (possibly qualified) */ + List *options; /* List of DefElem nodes */ +} AlterTypeStmt; /* ---------------------- * Create Rule Statement diff --git a/src/test/regress/expected/create_type.out b/src/test/regress/expected/create_type.out index eb55e25..86a8b65 100644 --- a/src/test/regress/expected/create_type.out +++ b/src/test/regress/expected/create_type.out @@ -224,3 +224,81 @@ select format_type('bpchar'::regtype, -1); bpchar (1 row) +-- +-- Test CREATE/ALTER TYPE using a type that's compatible with varchar, +-- so we can re-use those support functions +-- +CREATE TYPE myvarchar; +CREATE FUNCTION myvarcharin(cstring, oid, integer) RETURNS myvarchar +LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT AS 'varcharin'; +NOTICE: return type myvarchar is only a shell +CREATE FUNCTION myvarcharout(myvarchar) RETURNS cstring +LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT AS 'varcharout'; +NOTICE: argument type myvarchar is only a shell +CREATE FUNCTION myvarcharsend(myvarchar) RETURNS bytea +LANGUAGE internal STABLE PARALLEL SAFE STRICT AS 'varcharsend'; +NOTICE: argument type myvarchar is only a shell +CREATE FUNCTION myvarcharrecv(internal, oid, integer) RETURNS myvarchar +LANGUAGE internal STABLE PARALLEL SAFE STRICT AS 'varcharrecv'; +NOTICE: return type myvarchar is only a shell +-- fail, it's still a shell: +ALTER TYPE myvarchar SET (storage = extended); +ERROR: type "myvarchar" is only a shell +CREATE TYPE myvarchar ( + input = myvarcharin, + output = myvarcharout, + alignment = integer, + storage = main +); +-- want to check updating of a domain over the target type, too +CREATE DOMAIN myvarchardom AS myvarchar; +ALTER TYPE myvarchar SET (storage = plain); -- not allowed +ERROR: cannot change type's storage to PLAIN +ALTER TYPE myvarchar SET (storage = extended); +ALTER TYPE myvarchar SET ( + send = myvarcharsend, + receive = myvarcharrecv, + typmod_in = varchartypmodin, + typmod_out = varchartypmodout, + analyze = array_typanalyze -- bogus, but it doesn't matter +); +SELECT typinput, typoutput, typreceive, typsend, typmodin, typmodout, + typanalyze, typstorage +FROM pg_type WHERE typname = 'myvarchar'; + typinput | typoutput | typreceive | typsend | typmodin | typmodout | typanalyze | typstorage +-------------+--------------+---------------+---------------+-----------------+------------------+------------------+------------ + myvarcharin | myvarcharout | myvarcharrecv | myvarcharsend | varchartypmodin | varchartypmodout | array_typanalyze | x +(1 row) + +SELECT typinput, typoutput, typreceive, typsend, typmodin, typmodout, + typanalyze, typstorage +FROM pg_type WHERE typname = 'myvarchardom'; + typinput | typoutput | typreceive | typsend | typmodin | typmodout | typanalyze | typstorage +-----------+--------------+-------------+---------------+----------+-----------+------------------+------------ + domain_in | myvarcharout | domain_recv | myvarcharsend | - | - | array_typanalyze | x +(1 row) + +-- ensure dependencies are straight +DROP FUNCTION myvarcharsend(myvarchar); -- fail +ERROR: cannot drop function myvarcharsend(myvarchar) because other objects depend on it +DETAIL: type myvarchar depends on function myvarcharsend(myvarchar) +function myvarcharin(cstring,oid,integer) depends on type myvarchar +function myvarcharout(myvarchar) depends on type myvarchar +function myvarcharrecv(internal,oid,integer) depends on type myvarchar +type myvarchardom depends on function myvarcharsend(myvarchar) +HINT: Use DROP ... CASCADE to drop the dependent objects too. +DROP TYPE myvarchar; -- fail +ERROR: cannot drop type myvarchar because other objects depend on it +DETAIL: function myvarcharin(cstring,oid,integer) depends on type myvarchar +function myvarcharout(myvarchar) depends on type myvarchar +function myvarcharsend(myvarchar) depends on type myvarchar +function myvarcharrecv(internal,oid,integer) depends on type myvarchar +type myvarchardom depends on type myvarchar +HINT: Use DROP ... CASCADE to drop the dependent objects too. +DROP TYPE myvarchar CASCADE; +NOTICE: drop cascades to 5 other objects +DETAIL: drop cascades to function myvarcharin(cstring,oid,integer) +drop cascades to function myvarcharout(myvarchar) +drop cascades to function myvarcharsend(myvarchar) +drop cascades to function myvarcharrecv(internal,oid,integer) +drop cascades to type myvarchardom diff --git a/src/test/regress/sql/create_type.sql b/src/test/regress/sql/create_type.sql index 68b04fd..5b176bb 100644 --- a/src/test/regress/sql/create_type.sql +++ b/src/test/regress/sql/create_type.sql @@ -166,3 +166,60 @@ select format_type('varchar'::regtype, 42); select format_type('bpchar'::regtype, null); -- this behavior difference is intentional select format_type('bpchar'::regtype, -1); + +-- +-- Test CREATE/ALTER TYPE using a type that's compatible with varchar, +-- so we can re-use those support functions +-- +CREATE TYPE myvarchar; + +CREATE FUNCTION myvarcharin(cstring, oid, integer) RETURNS myvarchar +LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT AS 'varcharin'; + +CREATE FUNCTION myvarcharout(myvarchar) RETURNS cstring +LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT AS 'varcharout'; + +CREATE FUNCTION myvarcharsend(myvarchar) RETURNS bytea +LANGUAGE internal STABLE PARALLEL SAFE STRICT AS 'varcharsend'; + +CREATE FUNCTION myvarcharrecv(internal, oid, integer) RETURNS myvarchar +LANGUAGE internal STABLE PARALLEL SAFE STRICT AS 'varcharrecv'; + +-- fail, it's still a shell: +ALTER TYPE myvarchar SET (storage = extended); + +CREATE TYPE myvarchar ( + input = myvarcharin, + output = myvarcharout, + alignment = integer, + storage = main +); + +-- want to check updating of a domain over the target type, too +CREATE DOMAIN myvarchardom AS myvarchar; + +ALTER TYPE myvarchar SET (storage = plain); -- not allowed + +ALTER TYPE myvarchar SET (storage = extended); + +ALTER TYPE myvarchar SET ( + send = myvarcharsend, + receive = myvarcharrecv, + typmod_in = varchartypmodin, + typmod_out = varchartypmodout, + analyze = array_typanalyze -- bogus, but it doesn't matter +); + +SELECT typinput, typoutput, typreceive, typsend, typmodin, typmodout, + typanalyze, typstorage +FROM pg_type WHERE typname = 'myvarchar'; + +SELECT typinput, typoutput, typreceive, typsend, typmodin, typmodout, + typanalyze, typstorage +FROM pg_type WHERE typname = 'myvarchardom'; + +-- ensure dependencies are straight +DROP FUNCTION myvarcharsend(myvarchar); -- fail +DROP TYPE myvarchar; -- fail + +DROP TYPE myvarchar CASCADE;
pgsql-hackers by date: