postgres.git / summary / log / commit / refs

commit    ba2ea49b3cba05a948d38afe6191a4338e063dce
Author:   Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date:     Thu Aug 27 11:31:44 2026 +0000

    Don't create a shell type for function returning an array
    
    Refactor the checks in the function to move all the conditions for
    when to attempt creating a shell type into one place.  Add a check for
    the array syntax.
    
    In addition to rejecting array syntax, another user-visible effect is
    that the error message is now different if the type specified a
    typmod.  You now get "type does not exist" instead of the more
    specific "type modifier cannot be specified for shell type".  That
    seems better; the implicit shell type creation exists only for
    backwards compatibility, and it never worked with type modifiers, so
    if there's a type modifier it's most likely not because the user tried
    to create a shell type,
    
    Add test for the array syntax, the type modifier, and some other cases
    for which we don't create shell types.
    
    Discussion: https://www.postgresql.org/message-id/de673feb-41b4-4685-b24b-6408b95e58ab@iki.fi
    Backpatch-through: 14


src/backend/commands/functioncmds.c | 58 ++++++++++++++---------- src/test/regress/expected/create_type.out | 22 ++++++++- src/test/regress/input/create_function_1.source | 25 ++++++++++ src/test/regress/output/create_function_1.source | 24 ++++++++++ src/test/regress/sql/create_type.sql | 16 +++++++ 5 files changed, 121 insertions(+), 24 deletions(-) diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index 0434f197960..06ffb8e91ff 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -97,11 +97,32 @@ compute_return_type(TypeName *returnType, Oid languageOid, Oid rettype; Type typtup; AclResult aclresult; + bool attempt_shell_creation; - typtup = LookupTypeName(NULL, returnType, NULL, false); + /* + * If this looks like it could be an input function, and the type doesn't + * exist, we'll create it as a shell type. + * + * If the type name contains any modifiers like %TYPE, type[] array + * syntax, or typmod decoration, it's not an input function, or at least + * not one for which we'd want to automatically create a shell type. + * + * Only C-coded functions can be I/O functions. We enforce this + * restriction here mainly to prevent littering the catalogs with shell + * types due to simple typos in user-defined function definitions. + */ + attempt_shell_creation = + !returnType->pct_type && returnType->arrayBounds == NULL && + returnType->typmods == NIL && + (languageOid == INTERNALlanguageId || languageOid == ClanguageId); + typtup = LookupTypeName(NULL, returnType, NULL, false); if (typtup) { + /* + * Found an existing type with the given name. Check if it's a shell + * type. + */ if (!((Form_pg_type) GETSTRUCT(typtup))->typisdefined) { if (languageOid == SQLlanguageId) @@ -118,38 +139,28 @@ compute_return_type(TypeName *returnType, Oid languageOid, rettype = typeTypeId(typtup); ReleaseSysCache(typtup); } + else if (!attempt_shell_creation) + { + /* Type not found and we don't want to create a shell type */ + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("type \"%s\" does not exist", + TypeNameToString(returnType)))); + } else { - char *typnam = TypeNameToString(returnType); + /* Make a shell type */ Oid namespaceId; AclResult aclresult; char *typname; ObjectAddress address; - /* - * Only C-coded functions can be I/O functions. We enforce this - * restriction here mainly to prevent littering the catalogs with - * shell types due to simple typos in user-defined function - * definitions. - */ - if (languageOid != INTERNALlanguageId && - languageOid != ClanguageId) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("type \"%s\" does not exist", typnam))); - - /* Reject if there's typmod decoration, too */ - if (returnType->typmods != NIL) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("type modifier cannot be specified for shell type \"%s\"", - typnam))); - - /* Otherwise, go ahead and make a shell type */ ereport(NOTICE, (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("type \"%s\" is not yet defined", typnam), + errmsg("type \"%s\" is not yet defined", + TypeNameToString(returnType)), errdetail("Creating a shell type definition."))); + namespaceId = QualifiedNameGetCreationNamespace(returnType->names, &typname); aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), @@ -157,6 +168,7 @@ compute_return_type(TypeName *returnType, Oid languageOid, if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, OBJECT_SCHEMA, get_namespace_name(namespaceId)); + address = TypeShellMake(typname, namespaceId, GetUserId()); rettype = address.objectId; Assert(OidIsValid(rettype)); diff --git a/src/test/regress/expected/create_type.out b/src/test/regress/expected/create_type.out index 14394cc95cd..a2edf999c88 100644 --- a/src/test/regress/expected/create_type.out +++ b/src/test/regress/expected/create_type.out @@ -244,12 +244,26 @@ 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 +-- fail: typmods not allowed for a shell type +CREATE FUNCTION myvarchar_lower(text) RETURNS myvarchar(100) +LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT AS 'lower'; +ERROR: type modifier cannot be specified for shell type "myvarchar" +CREATE FUNCTION myvarchar_lower(myvarchar(100)) RETURNS text +LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT AS 'lower'; +ERROR: type modifier cannot be specified for shell type "myvarchar" CREATE TYPE myvarchar ( input = myvarcharin, output = myvarcharout, alignment = integer, storage = main ); +-- fail: typmods not allowed because 'typmod_in' / 'typmod_out' were not specified. +CREATE FUNCTION myvarchar_lower(text) RETURNS myvarchar(100) +LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT AS 'lower'; +ERROR: type modifier is not allowed for type "myvarchar" +CREATE FUNCTION myvarchar_lower(myvarchar(100)) RETURNS text +LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT AS 'lower'; +ERROR: type modifier is not allowed for type "myvarchar" -- 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 @@ -296,6 +310,9 @@ FROM pg_type WHERE typname = '_myvarchardom'; array_in | array_out | array_recv | array_send | - | - | array_typanalyze | array_subscript_handler | x (1 row) +-- typmods are now accepted in CREATE FUNCTION, although they are not stored +CREATE FUNCTION myvarchar_lower(myvarchar(100)) RETURNS myvarchar(100) +LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT AS 'lower'; -- ensure dependencies are straight DROP FUNCTION myvarcharsend(myvarchar); -- fail ERROR: cannot drop function myvarcharsend(myvarchar) because other objects depend on it @@ -303,6 +320,7 @@ 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 +function myvarchar_lower(myvarchar) 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 @@ -312,11 +330,13 @@ 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 +function myvarchar_lower(myvarchar) 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 +NOTICE: drop cascades to 6 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 +drop cascades to function myvarchar_lower(myvarchar) diff --git a/src/test/regress/input/create_function_1.source b/src/test/regress/input/create_function_1.source index 79a41562bb0..2235ccc5951 100644 --- a/src/test/regress/input/create_function_1.source +++ b/src/test/regress/input/create_function_1.source @@ -23,3 +23,28 @@ CREATE FUNCTION int44out(city_budget) RETURNS cstring AS '@libdir@/regress@DLSUFFIX@' LANGUAGE C STRICT IMMUTABLE; + + +-- If the specified type includes typmods or array syntax, don't create a shell type +CREATE FUNCTION bogus_in(cstring) + RETURNS bogus_shell(123) + AS '@libdir@/regress@DLSUFFIX@', 'widget_in' + LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION bogus_in(cstring) + RETURNS bogus_shell[] + AS '@libdir@/regress@DLSUFFIX@', 'widget_in' + LANGUAGE C STRICT IMMUTABLE; + +-- If the column specified with %TYPE does not exist, don't try to create a shell type +CREATE TEMP TABLE bogus_tbl (col int); +CREATE FUNCTION bogus_in(cstring) + RETURNS bogus_tbl.nonexistent_col%TYPE + AS '@libdir@/regress@DLSUFFIX@', 'widget_in' + LANGUAGE C STRICT IMMUTABLE; + +-- If the schema does not exist, don't try to create a shell type +CREATE FUNCTION bogus_in(cstring) + RETURNS nonexistent_schema.bogus_shell + AS '@libdir@/regress@DLSUFFIX@', 'widget_in' + LANGUAGE C STRICT IMMUTABLE; diff --git a/src/test/regress/output/create_function_1.source b/src/test/regress/output/create_function_1.source index 616b610e862..41318db7401 100644 --- a/src/test/regress/output/create_function_1.source +++ b/src/test/regress/output/create_function_1.source @@ -24,3 +24,27 @@ CREATE FUNCTION int44out(city_budget) AS '@libdir@/regress@DLSUFFIX@' LANGUAGE C STRICT IMMUTABLE; NOTICE: argument type city_budget is only a shell +-- If the specified type includes typmods or array syntax, don't create a shell type +CREATE FUNCTION bogus_in(cstring) + RETURNS bogus_shell(123) + AS '@libdir@/regress@DLSUFFIX@', 'widget_in' + LANGUAGE C STRICT IMMUTABLE; +ERROR: type "bogus_shell" does not exist +CREATE FUNCTION bogus_in(cstring) + RETURNS bogus_shell[] + AS '@libdir@/regress@DLSUFFIX@', 'widget_in' + LANGUAGE C STRICT IMMUTABLE; +ERROR: type "bogus_shell[]" does not exist +-- If the column specified with %TYPE does not exist, don't try to create a shell type +CREATE TEMP TABLE bogus_tbl (col int); +CREATE FUNCTION bogus_in(cstring) + RETURNS bogus_tbl.nonexistent_col%TYPE + AS '@libdir@/regress@DLSUFFIX@', 'widget_in' + LANGUAGE C STRICT IMMUTABLE; +ERROR: column "nonexistent_col" of relation "bogus_tbl" does not exist +-- If the schema does not exist, don't try to create a shell type +CREATE FUNCTION bogus_in(cstring) + RETURNS nonexistent_schema.bogus_shell + AS '@libdir@/regress@DLSUFFIX@', 'widget_in' + LANGUAGE C STRICT IMMUTABLE; +ERROR: schema "nonexistent_schema" does not exist diff --git a/src/test/regress/sql/create_type.sql b/src/test/regress/sql/create_type.sql index a32a9e67957..62754caf4f9 100644 --- a/src/test/regress/sql/create_type.sql +++ b/src/test/regress/sql/create_type.sql @@ -188,6 +188,12 @@ LANGUAGE internal STABLE PARALLEL SAFE STRICT AS 'varcharrecv'; -- fail, it's still a shell: ALTER TYPE myvarchar SET (storage = extended); +-- fail: typmods not allowed for a shell type +CREATE FUNCTION myvarchar_lower(text) RETURNS myvarchar(100) +LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT AS 'lower'; +CREATE FUNCTION myvarchar_lower(myvarchar(100)) RETURNS text +LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT AS 'lower'; + CREATE TYPE myvarchar ( input = myvarcharin, output = myvarcharout, @@ -195,6 +201,12 @@ CREATE TYPE myvarchar ( storage = main ); +-- fail: typmods not allowed because 'typmod_in' / 'typmod_out' were not specified. +CREATE FUNCTION myvarchar_lower(text) RETURNS myvarchar(100) +LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT AS 'lower'; +CREATE FUNCTION myvarchar_lower(myvarchar(100)) RETURNS text +LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT AS 'lower'; + -- want to check updating of a domain over the target type, too CREATE DOMAIN myvarchardom AS myvarchar; @@ -228,6 +240,10 @@ SELECT typinput, typoutput, typreceive, typsend, typmodin, typmodout, typanalyze, typsubscript, typstorage FROM pg_type WHERE typname = '_myvarchardom'; +-- typmods are now accepted in CREATE FUNCTION, although they are not stored +CREATE FUNCTION myvarchar_lower(myvarchar(100)) RETURNS myvarchar(100) +LANGUAGE internal IMMUTABLE PARALLEL SAFE STRICT AS 'lower'; + -- ensure dependencies are straight DROP FUNCTION myvarcharsend(myvarchar); -- fail DROP TYPE myvarchar; -- fail [parent: 9a1fa1f36e48]