diff options
| author | Heikki Linnakangas | 2026-08-27 11:31:44 +0000 |
|---|---|---|
| committer | Heikki Linnakangas | 2026-08-27 11:32:09 +0000 |
| commit | b62206043be298dfdf493d5dc4bcac9e573a99ce (patch) | |
| tree | f51326bf3918899ef20338ce8142ceca18262f2f | |
| parent | 92c0182141cf6355f5ca38b282531aca64068d4e (diff) | |
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
| -rw-r--r-- | src/backend/commands/functioncmds.c | 58 | ||||
| -rw-r--r-- | src/test/regress/expected/create_type.out | 46 | ||||
| -rw-r--r-- | src/test/regress/sql/create_type.sql | 42 |
3 files changed, 122 insertions, 24 deletions
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index d43b89d3efa..28c38fdbcb5 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -91,11 +91,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) @@ -112,37 +133,27 @@ 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; 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 = object_aclcheck(NamespaceRelationId, namespaceId, GetUserId(), @@ -150,6 +161,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 7383fcdbb12..a5f87faa19c 100644 --- a/src/test/regress/expected/create_type.out +++ b/src/test/regress/expected/create_type.out @@ -47,6 +47,30 @@ CREATE TYPE city_budget ( category = 'x', -- just to verify the system will take it preferred = true -- ditto ); +-- 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 :'regresslib', 'widget_in' + LANGUAGE C STRICT IMMUTABLE; +ERROR: type "bogus_shell" does not exist +CREATE FUNCTION bogus_in(cstring) + RETURNS bogus_shell[] + AS :'regresslib', '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 :'regresslib', '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 :'regresslib', 'widget_in' + LANGUAGE C STRICT IMMUTABLE; +ERROR: schema "nonexistent_schema" does not exist -- Test creation and destruction of shell types CREATE TYPE shell; CREATE TYPE shell; -- fail, type already present @@ -329,12 +353,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 @@ -381,6 +419,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 @@ -388,6 +429,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 @@ -397,11 +439,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/sql/create_type.sql b/src/test/regress/sql/create_type.sql index c25018029c2..4fd1facdffe 100644 --- a/src/test/regress/sql/create_type.sql +++ b/src/test/regress/sql/create_type.sql @@ -50,6 +50,32 @@ CREATE TYPE city_budget ( preferred = true -- ditto ); + +-- 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 :'regresslib', 'widget_in' + LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION bogus_in(cstring) + RETURNS bogus_shell[] + AS :'regresslib', '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 :'regresslib', '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 :'regresslib', 'widget_in' + LANGUAGE C STRICT IMMUTABLE; + + -- Test creation and destruction of shell types CREATE TYPE shell; CREATE TYPE shell; -- fail, type already present @@ -252,6 +278,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, @@ -259,6 +291,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; @@ -292,6 +330,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 |
