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]