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 b3272b49aa
commit e5e841cc53
3 changed files with 122 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,37 +139,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(),
@@ -156,6 +167,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

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

View File

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