summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHeikki Linnakangas2026-08-27 11:31:44 +0000
committerHeikki Linnakangas2026-08-27 11:32:09 +0000
commitb62206043be298dfdf493d5dc4bcac9e573a99ce (patch)
treef51326bf3918899ef20338ce8142ceca18262f2f
parent92c0182141cf6355f5ca38b282531aca64068d4e (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.c58
-rw-r--r--src/test/regress/expected/create_type.out46
-rw-r--r--src/test/regress/sql/create_type.sql42
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