1
0
mirror of https://github.com/postgres/postgres.git synced 2026-10-23 13:29:09 +03:00

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
This commit is contained in:
Heikki Linnakangas
2026-08-27 14:31:44 +03:00
parent 9a1fa1f36e
commit ba2ea49b3c
5 changed files with 121 additions and 24 deletions

View File

@@ -97,11 +97,32 @@ compute_return_type(TypeName *returnType, Oid languageOid,
Oid rettype;
Type typtup;
AclResult aclresult;
bool attempt_shell_creation;
/*
* 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));

View File

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

View File

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

View File

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

View File

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