Pavel Stehule <pavel.steh...@gmail.com> writes: > Tom Lane <t...@sss.pgh.pa.us> napsal: >> Yeah, that's what I said. But does it really add anything beyond the >> proposed text "A function returning a polymorphic type must have at least >> one matching polymorphic argument"? I don't think it'd be terribly >> helpful to say "A function returning anyelement must have at least one >> anyelement, anyarray, anynonarray, anyenum, or anyrange argument", and >> for sure such an error message would be a pain to maintain.
> The error message in your first patch is ok for all types without anyrange. > A behave of this type is more strict and +/- different than from other > polymorphic types. Well, here's a version that does it like that, but personally I find these messages too verbose and not an improvement on what I had before. (This is also rebased over the stuff I committed yesterday.) regards, tom lane
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c index 0b7face..7d887ea 100644 --- a/src/backend/catalog/pg_aggregate.c +++ b/src/backend/catalog/pg_aggregate.c @@ -93,8 +93,6 @@ AggregateCreate(const char *aggName, Oid mfinalfn = InvalidOid; /* can be omitted */ Oid sortop = InvalidOid; /* can be omitted */ Oid *aggArgTypes = parameterTypes->values; - bool hasPolyArg; - bool hasInternalArg; bool mtransIsStrict = false; Oid rettype; Oid finaltype; @@ -103,6 +101,7 @@ AggregateCreate(const char *aggName, int nargs_finalfn; Oid procOid; TupleDesc tupDesc; + char *detailmsg; int i; ObjectAddress myself, referenced; @@ -131,36 +130,33 @@ AggregateCreate(const char *aggName, FUNC_MAX_ARGS - 1, FUNC_MAX_ARGS - 1))); - /* check for polymorphic and INTERNAL arguments */ - hasPolyArg = false; - hasInternalArg = false; - for (i = 0; i < numArgs; i++) - { - if (IsPolymorphicType(aggArgTypes[i])) - hasPolyArg = true; - else if (aggArgTypes[i] == INTERNALOID) - hasInternalArg = true; - } - /* * If transtype is polymorphic, must have polymorphic argument also; else * we will have no way to deduce the actual transtype. */ - if (IsPolymorphicType(aggTransType) && !hasPolyArg) + detailmsg = check_valid_polymorphic_signature(aggTransType, + aggArgTypes, + numArgs); + if (detailmsg) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("cannot determine transition data type"), - errdetail("An aggregate using a polymorphic transition type must have at least one polymorphic argument."))); + errdetail_internal("%s", detailmsg))); /* * Likewise for moving-aggregate transtype, if any */ - if (OidIsValid(aggmTransType) && - IsPolymorphicType(aggmTransType) && !hasPolyArg) - ereport(ERROR, - (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), - errmsg("cannot determine transition data type"), - errdetail("An aggregate using a polymorphic transition type must have at least one polymorphic argument."))); + if (OidIsValid(aggmTransType)) + { + detailmsg = check_valid_polymorphic_signature(aggmTransType, + aggArgTypes, + numArgs); + if (detailmsg) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("cannot determine transition data type"), + errdetail_internal("%s", detailmsg))); + } /* * An ordered-set aggregate that is VARIADIC must be VARIADIC ANY. In @@ -492,12 +488,14 @@ AggregateCreate(const char *aggName, * that itself violates the rule against polymorphic result with no * polymorphic input.) */ - if (IsPolymorphicType(finaltype) && !hasPolyArg) + detailmsg = check_valid_polymorphic_signature(finaltype, + aggArgTypes, + numArgs); + if (detailmsg) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("cannot determine result data type"), - errdetail("An aggregate returning a polymorphic type " - "must have at least one polymorphic argument."))); + errdetail_internal("%s", detailmsg))); /* * Also, the return type can't be INTERNAL unless there's at least one @@ -505,11 +503,14 @@ AggregateCreate(const char *aggName, * for regular functions, but at the level of aggregates. We must test * this explicitly because we allow INTERNAL as the transtype. */ - if (finaltype == INTERNALOID && !hasInternalArg) + detailmsg = check_valid_internal_signature(finaltype, + aggArgTypes, + numArgs); + if (detailmsg) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("unsafe use of pseudo-type \"internal\""), - errdetail("A function returning \"internal\" must have at least one \"internal\" argument."))); + errdetail_internal("%s", detailmsg))); /* * If a moving-aggregate implementation is supplied, look up its finalfn diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index 423fd79..0cac936 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -32,6 +32,7 @@ #include "mb/pg_wchar.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" #include "parser/parse_type.h" #include "tcop/pquery.h" #include "tcop/tcopprot.h" @@ -97,12 +98,6 @@ ProcedureCreate(const char *procedureName, int allParamCount; Oid *allParams; char *paramModes = NULL; - bool genericInParam = false; - bool genericOutParam = false; - bool anyrangeInParam = false; - bool anyrangeOutParam = false; - bool internalInParam = false; - bool internalOutParam = false; Oid variadicType = InvalidOid; Acl *proacl = NULL; Relation rel; @@ -116,6 +111,7 @@ ProcedureCreate(const char *procedureName, bool is_update; ObjectAddress myself, referenced; + char *detailmsg; int i; Oid trfid; @@ -178,29 +174,34 @@ ProcedureCreate(const char *procedureName, } /* - * Detect whether we have polymorphic or INTERNAL arguments. The first - * loop checks input arguments, the second output arguments. + * Do not allow polymorphic return type unless there is a polymorphic + * input argument that we can use to deduce the actual return type. */ - for (i = 0; i < parameterCount; i++) - { - switch (parameterTypes->values[i]) - { - case ANYARRAYOID: - case ANYELEMENTOID: - case ANYNONARRAYOID: - case ANYENUMOID: - genericInParam = true; - break; - case ANYRANGEOID: - genericInParam = true; - anyrangeInParam = true; - break; - case INTERNALOID: - internalInParam = true; - break; - } - } + detailmsg = check_valid_polymorphic_signature(returnType, + parameterTypes->values, + parameterCount); + if (detailmsg) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("cannot determine result data type"), + errdetail_internal("%s", detailmsg))); + /* + * Also, do not allow return type INTERNAL unless at least one input + * argument is INTERNAL. + */ + detailmsg = check_valid_internal_signature(returnType, + parameterTypes->values, + parameterCount); + if (detailmsg) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("unsafe use of pseudo-type \"internal\""), + errdetail_internal("%s", detailmsg))); + + /* + * Apply the same tests to any OUT arguments. + */ if (allParameterTypes != PointerGetDatum(NULL)) { for (i = 0; i < allParamCount; i++) @@ -210,52 +211,26 @@ ProcedureCreate(const char *procedureName, paramModes[i] == PROARGMODE_VARIADIC) continue; /* ignore input-only params */ - switch (allParams[i]) - { - case ANYARRAYOID: - case ANYELEMENTOID: - case ANYNONARRAYOID: - case ANYENUMOID: - genericOutParam = true; - break; - case ANYRANGEOID: - genericOutParam = true; - anyrangeOutParam = true; - break; - case INTERNALOID: - internalOutParam = true; - break; - } + detailmsg = check_valid_polymorphic_signature(allParams[i], + parameterTypes->values, + parameterCount); + if (detailmsg) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("cannot determine result data type"), + errdetail_internal("%s", detailmsg))); + detailmsg = check_valid_internal_signature(allParams[i], + parameterTypes->values, + parameterCount); + if (detailmsg) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("unsafe use of pseudo-type \"internal\""), + errdetail_internal("%s", detailmsg))); } } - /* - * Do not allow polymorphic return type unless at least one input argument - * is polymorphic. ANYRANGE return type is even stricter: must have an - * ANYRANGE input (since we can't deduce the specific range type from - * ANYELEMENT). Also, do not allow return type INTERNAL unless at least - * one input argument is INTERNAL. - */ - if ((IsPolymorphicType(returnType) || genericOutParam) - && !genericInParam) - ereport(ERROR, - (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), - errmsg("cannot determine result data type"), - errdetail("A function returning a polymorphic type must have at least one polymorphic argument."))); - - if ((returnType == ANYRANGEOID || anyrangeOutParam) && - !anyrangeInParam) - ereport(ERROR, - (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), - errmsg("cannot determine result data type"), - errdetail("A function returning \"anyrange\" must have at least one \"anyrange\" argument."))); - - if ((returnType == INTERNALOID || internalOutParam) && !internalInParam) - ereport(ERROR, - (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), - errmsg("unsafe use of pseudo-type \"internal\""), - errdetail("A function returning \"internal\" must have at least one \"internal\" argument."))); - + /* Identify variadic argument type, if any */ if (paramModes != NULL) { /* diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index 4a2b463..7a9b303 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -1966,6 +1966,77 @@ enforce_generic_type_consistency(const Oid *actual_arg_types, return rettype; } +/* + * check_valid_polymorphic_signature() + * Is a proposed function signature valid per polymorphism rules? + * + * Returns NULL if the signature is valid (either ret_type is not polymorphic, + * or it can be deduced from the given declared argument types). Otherwise, + * returns a palloc'd, already translated errdetail string saying why not. + */ +char * +check_valid_polymorphic_signature(Oid ret_type, + const Oid *declared_arg_types, + int nargs) +{ + if (ret_type == ANYRANGEOID) + { + /* + * ANYRANGE requires an ANYRANGE input, else we can't tell which of + * several range types with the same element type to use. + */ + for (int i = 0; i < nargs; i++) + { + if (declared_arg_types[i] == ret_type) + return NULL; /* OK */ + } + return psprintf(_("A result of type %s requires at least one input of type %s."), + format_type_be(ret_type), format_type_be(ret_type)); + } + else if (IsPolymorphicType(ret_type)) + { + /* Otherwise, any polymorphic type can be deduced from any other */ + for (int i = 0; i < nargs; i++) + { + if (IsPolymorphicType(declared_arg_types[i])) + return NULL; /* OK */ + } + return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange."), + format_type_be(ret_type)); + } + else + return NULL; /* OK, ret_type is not polymorphic */ +} + +/* + * check_valid_internal_signature() + * Is a proposed function signature valid per INTERNAL safety rules? + * + * Returns NULL if OK, or a suitable error message if ret_type is INTERNAL but + * none of the declared arg types are. (It's unsafe to create such a function + * since it would allow invocation of INTERNAL-consuming functions directly + * from SQL.) It's overkill to return the error detail message, since there + * is only one possibility, but we do it like this to keep the API similar to + * check_valid_polymorphic_signature(). + */ +char * +check_valid_internal_signature(Oid ret_type, + const Oid *declared_arg_types, + int nargs) +{ + if (ret_type == INTERNALOID) + { + for (int i = 0; i < nargs; i++) + { + if (declared_arg_types[i] == ret_type) + return NULL; /* OK */ + } + return pstrdup(_("A result of type internal requires at least one input of type internal.")); + } + else + return NULL; /* OK, ret_type is not INTERNAL */ +} + /* TypeCategory() * Assign a category to the specified type OID. diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h index ff9219d..8686eaa 100644 --- a/src/include/parser/parse_coerce.h +++ b/src/include/parser/parse_coerce.h @@ -80,6 +80,13 @@ extern Oid enforce_generic_type_consistency(const Oid *actual_arg_types, Oid rettype, bool allow_poly); +extern char *check_valid_polymorphic_signature(Oid ret_type, + const Oid *declared_arg_types, + int nargs); +extern char *check_valid_internal_signature(Oid ret_type, + const Oid *declared_arg_types, + int nargs); + extern CoercionPathType find_coercion_pathway(Oid targetTypeId, Oid sourceTypeId, CoercionContext ccontext, diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out index 986417a..1573753 100644 --- a/src/test/regress/expected/polymorphism.out +++ b/src/test/regress/expected/polymorphism.out @@ -81,7 +81,7 @@ CREATE AGGREGATE myaggp01a(*) (SFUNC = stfnp, STYPE = int4[], CREATE AGGREGATE myaggp02a(*) (SFUNC = stfnp, STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. +DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange. -- N P -- should CREATE CREATE AGGREGATE myaggp03a(*) (SFUNC = stfp, STYPE = int4[], @@ -93,11 +93,11 @@ CREATE AGGREGATE myaggp03b(*) (SFUNC = stfp, STYPE = int4[], CREATE AGGREGATE myaggp04a(*) (SFUNC = stfp, STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. +DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange. CREATE AGGREGATE myaggp04b(*) (SFUNC = stfp, STYPE = anyarray, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. +DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange. -- Case2 (R = P) && ((B = P) || (B = N)) -- ------------------------------------- -- S tf1 B tf2 @@ -152,13 +152,13 @@ ERROR: function tfp(integer[], anyelement) does not exist CREATE AGGREGATE myaggp13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. +DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange. -- P N N P -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement) CREATE AGGREGATE myaggp14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. +DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange. -- P N P N -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int) CREATE AGGREGATE myaggp15a(BASETYPE = anyelement, SFUNC = tfnp, @@ -174,21 +174,21 @@ ERROR: function tf2p(anyarray, anyelement) does not exist CREATE AGGREGATE myaggp17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. +DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange. CREATE AGGREGATE myaggp17b(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. +DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange. -- P P N P -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement) CREATE AGGREGATE myaggp18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. +DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange. CREATE AGGREGATE myaggp18b(BASETYPE = int, SFUNC = tfp, STYPE = anyarray, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. +DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange. -- P P P N -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int) CREATE AGGREGATE myaggp19a(BASETYPE = anyelement, SFUNC = tf1p, @@ -218,11 +218,11 @@ CREATE AGGREGATE myaggn01b(*) (SFUNC = stfnp, STYPE = int4[], CREATE AGGREGATE myaggn02a(*) (SFUNC = stfnp, STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. +DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange. CREATE AGGREGATE myaggn02b(*) (SFUNC = stfnp, STYPE = anyarray, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. +DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange. -- N P -- should CREATE CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[], @@ -232,7 +232,7 @@ CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[], CREATE AGGREGATE myaggn04a(*) (SFUNC = stfp, STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. +DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange. -- Case4 (R = N) && ((B = P) || (B = N)) -- ------------------------------------- -- S tf1 B tf2 @@ -286,21 +286,21 @@ ERROR: function tfp(integer[], anyelement) does not exist CREATE AGGREGATE myaggn13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. +DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange. CREATE AGGREGATE myaggn13b(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. +DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange. -- P N N P -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement) CREATE AGGREGATE myaggn14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. +DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange. CREATE AGGREGATE myaggn14b(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. +DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange. -- P N P N -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int) CREATE AGGREGATE myaggn15a(BASETYPE = anyelement, SFUNC = tfnp, @@ -322,13 +322,13 @@ ERROR: function tf2p(anyarray, anyelement) does not exist CREATE AGGREGATE myaggn17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. +DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange. -- P P N P -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement) CREATE AGGREGATE myaggn18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. +DETAIL: A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange. -- P P P N -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int) CREATE AGGREGATE myaggn19a(BASETYPE = anyelement, SFUNC = tf1p, diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out index a70060b..cdfc43e 100644 --- a/src/test/regress/expected/rangefuncs.out +++ b/src/test/regress/expected/rangefuncs.out @@ -1556,7 +1556,7 @@ DROP FUNCTION dup(anyelement); CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray) AS 'select $1, array[$1,$1]' LANGUAGE sql; ERROR: cannot determine result data type -DETAIL: A function returning a polymorphic type must have at least one polymorphic argument. +DETAIL: A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange. -- -- table functions -- diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out index 348235a..a28741a 100644 --- a/src/test/regress/expected/rangetypes.out +++ b/src/test/regress/expected/rangetypes.out @@ -1371,12 +1371,12 @@ drop function anyarray_anyrange_func(anyarray, anyrange); create function bogus_func(anyelement) returns anyrange as 'select int4range(1,10)' language sql; ERROR: cannot determine result data type -DETAIL: A function returning "anyrange" must have at least one "anyrange" argument. +DETAIL: A result of type anyrange requires at least one input of type anyrange. -- should fail create function bogus_func(int) returns anyrange as 'select int4range(1,10)' language sql; ERROR: cannot determine result data type -DETAIL: A function returning a polymorphic type must have at least one polymorphic argument. +DETAIL: A result of type anyrange requires at least one input of type anyrange. create function range_add_bounds(anyrange) returns anyelement as 'select lower($1) + upper($1)' language sql; select range_add_bounds(int4range(1, 17)); @@ -1521,14 +1521,14 @@ select * from table_succeed(int4range(1,11)); create function outparam_fail(i anyelement, out r anyrange, out t text) as $$ select '[1,10]', 'foo' $$ language sql; ERROR: cannot determine result data type -DETAIL: A function returning "anyrange" must have at least one "anyrange" argument. +DETAIL: A result of type anyrange requires at least one input of type anyrange. --should fail create function inoutparam_fail(inout i anyelement, out r anyrange) as $$ select $1, '[1,10]' $$ language sql; ERROR: cannot determine result data type -DETAIL: A function returning "anyrange" must have at least one "anyrange" argument. +DETAIL: A result of type anyrange requires at least one input of type anyrange. --should fail create function table_fail(i anyelement) returns table(i anyelement, r anyrange) as $$ select $1, '[1,10]' $$ language sql; ERROR: cannot determine result data type -DETAIL: A function returning "anyrange" must have at least one "anyrange" argument. +DETAIL: A result of type anyrange requires at least one input of type anyrange.
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml index 410eaed..6407d3d 100644 --- a/doc/src/sgml/datatype.sgml +++ b/doc/src/sgml/datatype.sgml @@ -4784,6 +4784,14 @@ SELECT * FROM pg_attribute </indexterm> <indexterm zone="datatype-pseudo"> + <primary>anycompatible</primary> + </indexterm> + + <indexterm zone="datatype-pseudo"> + <primary>anycompatiblearray</primary> + </indexterm> + + <indexterm zone="datatype-pseudo"> <primary>void</primary> </indexterm> @@ -4889,6 +4897,34 @@ SELECT * FROM pg_attribute </row> <row> + <entry><type>anycompatible</type></entry> + <entry>Indicates that a function accepts any data type. Values + are converted to real common type. + (see <xref linkend="extend-types-polymorphic"/>).</entry> + </row> + + <row> + <entry><type>anycompatiblearray</type></entry> + <entry>Indicates that a function accepts any array data type. The + elements of array are converted to common type of these values. + (see <xref linkend="extend-types-polymorphic"/>).</entry> + </row> + + <row> + <entry><type>anycompatiblenonarray</type></entry> + <entry>Indicates that a function accepts any non-array data type + (see <xref linkend="extend-types-polymorphic"/>).</entry> + </row> + + <row> + <entry><type>anycompatiblerange</type></entry> + <entry>Indicates that a function accepts any range data type + (see <xref linkend="extend-types-polymorphic"/> and + <xref linkend="rangetypes"/>). The subtype can be used for + deduction of common type.</entry> + </row> + + <row> <entry><type>cstring</type></entry> <entry>Indicates that a function accepts or returns a null-terminated C string.</entry> </row> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 9ec1af7..05c0919 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -231,7 +231,7 @@ <para> Five pseudo-types of special interest are <type>anyelement</type>, <type>anyarray</type>, <type>anynonarray</type>, <type>anyenum</type>, - and <type>anyrange</type>, + <type>anyrange</type>, <type>anycompatible</type> and <type>anycompatiblearray</type>. which are collectively called <firstterm>polymorphic types</firstterm>. Any function declared using these types is said to be a <firstterm>polymorphic function</firstterm>. A polymorphic function can @@ -268,6 +268,15 @@ </para> <para> + Second family of polymorphic types are types <type>anycompatible</type> and + <type>anycompatiblearray</type>. These types are similar to types + <type>anyelement</type> and <type>anyarray</type>. The arguments declared + as <type>anyelement</type> requires same real type of passed values. For + <type>anycompatible</type>'s arguments is selected common type, and later + all these arguments are casted to this common type. + </para> + + <para> Thus, when more than one argument position is declared with a polymorphic type, the net effect is that only certain combinations of actual argument types are allowed. For example, a function declared as diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 76fd938..22540bb 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -404,10 +404,6 @@ ConstructTupleDescriptor(Relation heapRelation, */ keyType = amroutine->amkeytype; - /* - * Code below is concerned to the opclasses which are not used with - * the included columns. - */ if (i < indexInfo->ii_NumIndexKeyAttrs) { tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(classObjectId[i])); @@ -422,6 +418,10 @@ ConstructTupleDescriptor(Relation heapRelation, * If keytype is specified as ANYELEMENT, and opcintype is * ANYARRAY, then the attribute type must be an array (else it'd * not have matched this opclass); use its element type. + * + * We could also allow ANYCOMPATIBLE/ANYCOMPATIBLEARRAY here, but + * there seems no need to do so; there's no reason to declare an + * opclass as taking ANYCOMPATIBLEARRAY rather than ANYARRAY. */ if (keyType == ANYELEMENTOID && opclassTup->opcintype == ANYARRAYOID) { diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index 0cac936..6cdda35 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -262,6 +262,9 @@ ProcedureCreate(const char *procedureName, case ANYARRAYOID: variadicType = ANYELEMENTOID; break; + case ANYCOMPATIBLEARRAYOID: + variadicType = ANYCOMPATIBLEOID; + break; default: variadicType = get_element_type(allParams[i]); if (!OidIsValid(variadicType)) diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index 5eac55a..694114a 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -320,6 +320,7 @@ interpret_function_parameter_list(ParseState *pstate, switch (toid) { case ANYARRAYOID: + case ANYCOMPATIBLEARRAYOID: case ANYOID: /* okay */ break; diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index 7a9b303..cf199cb 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -167,15 +167,17 @@ coerce_type(ParseState *pstate, Node *node, } if (targetTypeId == ANYOID || targetTypeId == ANYELEMENTOID || - targetTypeId == ANYNONARRAYOID) + targetTypeId == ANYNONARRAYOID || + targetTypeId == ANYCOMPATIBLEOID || + targetTypeId == ANYCOMPATIBLENONARRAYOID) { /* * Assume can_coerce_type verified that implicit coercion is okay. * * Note: by returning the unmodified node here, we are saying that * it's OK to treat an UNKNOWN constant as a valid input for a - * function accepting ANY, ANYELEMENT, or ANYNONARRAY. This should be - * all right, since an UNKNOWN value is still a perfectly valid Datum. + * function accepting one of these pseudotypes. This should be all + * right, since an UNKNOWN value is still a perfectly valid Datum. * * NB: we do NOT want a RelabelType here: the exposed type of the * function argument must be its actual type, not the polymorphic @@ -185,7 +187,9 @@ coerce_type(ParseState *pstate, Node *node, } if (targetTypeId == ANYARRAYOID || targetTypeId == ANYENUMOID || - targetTypeId == ANYRANGEOID) + targetTypeId == ANYRANGEOID || + targetTypeId == ANYCOMPATIBLEARRAYOID || + targetTypeId == ANYCOMPATIBLERANGEOID) { /* * Assume can_coerce_type verified that implicit coercion is okay. @@ -193,10 +197,10 @@ coerce_type(ParseState *pstate, Node *node, * These cases are unlike the ones above because the exposed type of * the argument must be an actual array, enum, or range type. In * particular the argument must *not* be an UNKNOWN constant. If it - * is, we just fall through; below, we'll call anyarray_in, - * anyenum_in, or anyrange_in, which will produce an error. Also, if - * what we have is a domain over array, enum, or range, we have to - * relabel it to its base type. + * is, we just fall through; below, we'll call the pseudotype's input + * function, which will produce an error. Also, if what we have is a + * domain over array, enum, or range, we have to relabel it to its + * base type. * * Note: currently, we can't actually see a domain-over-enum here, * since the other functions in this file will not match such a @@ -1387,6 +1391,99 @@ select_common_type(ParseState *pstate, List *exprs, const char *context, } /* + * select_common_type_from_vector() + * Determine the common supertype of vector of Oids. + * + * Similar to select_common_type() but simplified for polymorphics + * type processing. When there are no supertype, then returns InvalidOid, + * when noerror is true, or raise exception when noerror is false. + */ +static Oid +select_common_type_from_vector(int nargs, Oid *typeids, bool noerror) +{ + int i = 0; + Oid ptype; + TYPCATEGORY pcategory; + bool pispreferred; + + Assert(nargs > 0); + ptype = typeids[0]; + + /* fast leave when all types are same */ + if (ptype != UNKNOWNOID) + { + for (i = 1; i < nargs; i++) + { + if (ptype != typeids[i]) + break; + } + + if (i == nargs) + return ptype; + } + + /* + * Nope, so set up for the full algorithm. Note that at this point, we can + * skip first i elements, because was checked in previous loop. + */ + ptype = getBaseType(ptype); + get_type_category_preferred(ptype, &pcategory, &pispreferred); + + for (; i < nargs; i++) + { + Oid ntype = getBaseType(typeids[i]); + + /* move on to next one if no new information... */ + if (ntype != UNKNOWNOID && ntype != ptype) + { + TYPCATEGORY ncategory; + bool nispreferred; + + get_type_category_preferred(ntype, &ncategory, &nispreferred); + + if (ptype == UNKNOWNOID) + { + /* so far, only unknowns so take anything... */ + ptype = ntype; + pcategory = ncategory; + pispreferred = nispreferred; + } + else if (ncategory != pcategory) + { + if (noerror) + return InvalidOid; + + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("types %s and %s cannot be matched", + format_type_be(ptype), + format_type_be(ntype)))); + } + else if (!pispreferred && + can_coerce_type(1, &ptype, &ntype, COERCION_IMPLICIT) && + !can_coerce_type(1, &ntype, &ptype, COERCION_IMPLICIT)) + { + /* + * take new type if can coerce to it implicitly but not the + * other way; but if we have a preferred type, stay on it. + */ + ptype = ntype; + pcategory = ncategory; + pispreferred = nispreferred; + } + } + } + + /* + * Be consistent with select_common_type() + */ + if (ptype == UNKNOWNOID) + ptype = TEXTOID; + + return ptype; +} + +/* * coerce_to_common_type() * Coerce an expression to the given type. * @@ -1445,6 +1542,13 @@ coerce_to_common_type(ParseState *pstate, Node *node, * we add the extra condition that the ANYELEMENT type must not be an array. * (This is a no-op if used in combination with ANYARRAY or ANYENUM, but * is an extra restriction if not.) + * 8) Previous rules are valid too for ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, + * ANYCOMPATIBLENONARRAY and ANYCOMPATIBLERANGE with following exception. + * The used datatypes should not be strictly same. These types should be + * from same type category, and for any used type there should be implicit + * cast to selected one of these types. + * 9) There is not any relation between ANY types and ANYCOMPATIBLE types. + * Isn't possible derive ANY type from ANYCOMPATIBLE type and vice versa. * * Domains over arrays match ANYARRAY, and are immediately flattened to their * base type. (Thus, for example, we will consider it a match if one ANYARRAY @@ -1482,6 +1586,12 @@ check_generic_type_consistency(const Oid *actual_arg_types, bool have_anyelement = false; bool have_anynonarray = false; bool have_anyenum = false; + bool have_anycompatible_nonarray = false; + bool have_anycompatible_range = false; + bool have_generic_anycompatible = false; + Oid anycompatible_range_typeid = InvalidOid; + Oid anycompatible_actual_types[FUNC_MAX_ARGS]; + int n_anycompatible_args = 0; /* * Loop through the arguments to see if we have any that are polymorphic. @@ -1525,6 +1635,74 @@ check_generic_type_consistency(const Oid *actual_arg_types, return false; range_typeid = actual_type; } + else if (decl_type == ANYCOMPATIBLEOID || + decl_type == ANYCOMPATIBLENONARRAYOID) + { + have_generic_anycompatible = true; + if (decl_type == ANYCOMPATIBLENONARRAYOID) + have_anycompatible_nonarray = true; + + /* An unknown literal is no help for resolving actual types */ + if (actual_type == UNKNOWNOID) + continue; + + /* collect used type, reduce repeated values */ + if (n_anycompatible_args == 0 || + anycompatible_actual_types[n_anycompatible_args - 1] != actual_type) + anycompatible_actual_types[n_anycompatible_args++] = actual_type; + } + else if (decl_type == ANYCOMPATIBLEARRAYOID) + { + Oid anycompatible_elem_type; + + have_generic_anycompatible = true; + + if (actual_type == UNKNOWNOID) + continue; + + actual_type = getBaseType(actual_type); /* flatten domains */ + anycompatible_elem_type = get_element_type(actual_type); + + if (!OidIsValid(anycompatible_elem_type)) + return false; + + /* collect used type, reduce repeated values */ + if (n_anycompatible_args == 0 || + anycompatible_actual_types[n_anycompatible_args - 1] != anycompatible_elem_type) + anycompatible_actual_types[n_anycompatible_args++] = anycompatible_elem_type; + } + else if (decl_type == ANYCOMPATIBLERANGEOID) + { + Oid anycompatible_range_typelem; + + have_generic_anycompatible = true; + have_anycompatible_range = true; + + if (actual_type == UNKNOWNOID) + continue; + actual_type = getBaseType(actual_type); /* flatten domains */ + + /* + * range type is used just for derivation of common type, but + * range types should be same. Same behave like anyrange - cast + * between ranges are not supported. + */ + if (OidIsValid(anycompatible_range_typeid) && + anycompatible_range_typeid != actual_type) + return false; + + anycompatible_range_typelem = get_range_subtype(actual_type); + if (!OidIsValid(anycompatible_range_typelem)) + return false; + + if (!OidIsValid(anycompatible_range_typeid)) + anycompatible_range_typeid = actual_type; + + /* collect used type, reduce repeated values */ + if (n_anycompatible_args == 0 || + anycompatible_actual_types[n_anycompatible_args - 1] != anycompatible_range_typelem) + anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem; + } } /* Get the element type based on the array type, if we have one */ @@ -1591,6 +1769,36 @@ check_generic_type_consistency(const Oid *actual_arg_types, return false; } + /* check anycompatible collected data */ + if (have_generic_anycompatible) + { + /* we can check type consisteny when we have some not unknown types */ + if (n_anycompatible_args > 0) + { + Oid anycompatible_typeid; + + anycompatible_typeid = select_common_type_from_vector(n_anycompatible_args, + anycompatible_actual_types, + true); + + if (!OidIsValid(anycompatible_typeid)) + return false; + + if (have_anycompatible_nonarray) + { + /* + * require the anycompatible type to not be an array or domain + * over array + */ + if (type_is_array_domain(anycompatible_typeid)) + return false; + } + + if (have_anycompatible_range && !OidIsValid(anycompatible_range_typeid)) + return false; + } + } + /* Looks valid */ return true; } @@ -1643,6 +1851,16 @@ check_generic_type_consistency(const Oid *actual_arg_types, * we add the extra condition that the ANYELEMENT type must not be an array. * (This is a no-op if used in combination with ANYARRAY or ANYENUM, but * is an extra restriction if not.) + * 10) The relation between types from ANY family type are same like + * relations between types from ANYCOMPATIBLE family type, with one + * difference. The parameters with type from ANY family type requires + * exactly one actual type. The arguments with ANYCOMPATIBLE family type + * allows types that shares type category. Later polymorphic type is + * replaced by real type. This real type is one from actual types + * (polymorphic argument actual types) for that is available implicit + * cast from type of any related polymorphic arguments. + * 11) The arguments with ANY family type and ANYCOMPATIBLE family type + * are independent. * * Domains over arrays or ranges match ANYARRAY or ANYRANGE arguments, * respectively, and are immediately flattened to their base type. (In @@ -1673,11 +1891,15 @@ enforce_generic_type_consistency(const Oid *actual_arg_types, bool allow_poly) { int j; - bool have_generics = false; + bool have_generics_any = false; + bool have_generics_anycompatible = false; bool have_unknowns = false; Oid elem_typeid = InvalidOid; Oid array_typeid = InvalidOid; Oid range_typeid = InvalidOid; + Oid anycompatible_typeid = InvalidOid; + Oid anycompatible_array_typeid = InvalidOid; + Oid anycompatible_range_typeid = InvalidOid; Oid array_typelem; Oid range_typelem; bool have_anyelement = (rettype == ANYELEMENTOID || @@ -1685,6 +1907,11 @@ enforce_generic_type_consistency(const Oid *actual_arg_types, rettype == ANYENUMOID); bool have_anynonarray = (rettype == ANYNONARRAYOID); bool have_anyenum = (rettype == ANYENUMOID); + bool have_anycompatible_nonarray = (rettype == ANYCOMPATIBLENONARRAYOID); + bool have_anycompatible_array = (rettype == ANYCOMPATIBLEARRAYOID); + bool have_anycompatible_range = (rettype == ANYCOMPATIBLERANGEOID); + Oid anycompatible_actual_types[FUNC_MAX_ARGS]; + int n_anycompatible_args = 0; /* * Loop through the arguments to see if we have any that are polymorphic. @@ -1699,7 +1926,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types, decl_type == ANYNONARRAYOID || decl_type == ANYENUMOID) { - have_generics = have_anyelement = true; + have_generics_any = have_anyelement = true; if (decl_type == ANYNONARRAYOID) have_anynonarray = true; else if (decl_type == ANYENUMOID) @@ -1722,14 +1949,18 @@ enforce_generic_type_consistency(const Oid *actual_arg_types, } else if (decl_type == ANYARRAYOID) { - have_generics = true; + have_generics_any = true; + have_anycompatible_array = true; + if (actual_type == UNKNOWNOID) { have_unknowns = true; continue; } + if (allow_poly && decl_type == actual_type) continue; /* no new information here */ + actual_type = getBaseType(actual_type); /* flatten domains */ if (OidIsValid(array_typeid) && actual_type != array_typeid) ereport(ERROR, @@ -1742,7 +1973,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types, } else if (decl_type == ANYRANGEOID) { - have_generics = true; + have_generics_any = true; if (actual_type == UNKNOWNOID) { have_unknowns = true; @@ -1760,128 +1991,300 @@ enforce_generic_type_consistency(const Oid *actual_arg_types, format_type_be(actual_type)))); range_typeid = actual_type; } - } + else if (decl_type == ANYCOMPATIBLEOID || + decl_type == ANYCOMPATIBLENONARRAYOID) + { + have_generics_anycompatible = true; - /* - * Fast Track: if none of the arguments are polymorphic, return the - * unmodified rettype. We assume it can't be polymorphic either. - */ - if (!have_generics) - return rettype; + if (decl_type == ANYCOMPATIBLENONARRAYOID) + have_anycompatible_nonarray = true; - /* Get the element type based on the array type, if we have one */ - if (OidIsValid(array_typeid)) - { - if (array_typeid == ANYARRAYOID && !have_anyelement) - { - /* Special case for ANYARRAY input: okay iff no ANYELEMENT */ - array_typelem = ANYELEMENTOID; + /* + * because declared type will be replaced every time, we don't + * need some special work for unknown types. + */ + if (actual_type == UNKNOWNOID) + continue; + + if (allow_poly && decl_type == actual_type) + continue; + + /* collect used type, reduce repeated values */ + if (n_anycompatible_args == 0 || + anycompatible_actual_types[n_anycompatible_args - 1] != actual_type) + anycompatible_actual_types[n_anycompatible_args++] = actual_type; } - else + else if (decl_type == ANYCOMPATIBLEARRAYOID) { - array_typelem = get_element_type(array_typeid); - if (!OidIsValid(array_typelem)) + Oid anycompatible_elem_type; + + have_generics_anycompatible = true; + have_anycompatible_array = true; + + if (actual_type == UNKNOWNOID) + continue; + + if (allow_poly && decl_type == actual_type) + continue; + + actual_type = getBaseType(actual_type); /* flatten domains */ + anycompatible_elem_type = get_element_type(actual_type); + + if (!OidIsValid(anycompatible_elem_type)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("argument declared %s is not an array but type %s", - "anyarray", format_type_be(array_typeid)))); - } + "anyarray", format_type_be(actual_type)))); - if (!OidIsValid(elem_typeid)) - { - /* - * if we don't have an element type yet, use the one we just got - */ - elem_typeid = array_typelem; + /* collect used type, reduce repeated values */ + if (n_anycompatible_args == 0 || + anycompatible_actual_types[n_anycompatible_args - 1] != anycompatible_elem_type) + anycompatible_actual_types[n_anycompatible_args++] = anycompatible_elem_type; } - else if (array_typelem != elem_typeid) + else if (decl_type == ANYCOMPATIBLERANGEOID) { - /* otherwise, they better match */ - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("argument declared %s is not consistent with argument declared %s", - "anyarray", "anyelement"), - errdetail("%s versus %s", - format_type_be(array_typeid), - format_type_be(elem_typeid)))); + Oid anycompatible_range_typelem; + + have_generics_anycompatible = true; + have_anycompatible_range = true; + + if (actual_type == UNKNOWNOID) + { + have_unknowns = true; + continue; + } + if (allow_poly && decl_type == actual_type) + continue; /* no new information here */ + actual_type = getBaseType(actual_type); /* flatten domains */ + + if (OidIsValid(anycompatible_range_typeid) && + actual_type != anycompatible_range_typeid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("arguments declared \"anycompatiblerange\" are not all alike"), + errdetail("%s versus %s", + format_type_be(anycompatible_range_typeid), + format_type_be(actual_type)))); + + anycompatible_range_typeid = actual_type; + anycompatible_range_typelem = get_range_subtype(anycompatible_range_typeid); + + if (!OidIsValid(anycompatible_range_typelem)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("argument declared %s is not a range type but type %s", + "anycompatiblerange", + format_type_be(anycompatible_range_typeid)))); + + /* collect used type, reduce repeated values */ + if (n_anycompatible_args == 0 || + anycompatible_actual_types[n_anycompatible_args - 1] != anycompatible_range_typelem) + anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem; } } - /* Get the element type based on the range type, if we have one */ - if (OidIsValid(range_typeid)) + /* + * Fast Track: if none of the arguments are polymorphic, return the + * unmodified rettype. We assume it can't be polymorphic either. + */ + if (!have_generics_any && !have_generics_anycompatible) + return rettype; + + if (have_generics_any) { - if (range_typeid == ANYRANGEOID && !have_anyelement) + /* Get the element type based on the array type, if we have one */ + if (OidIsValid(array_typeid)) { - /* Special case for ANYRANGE input: okay iff no ANYELEMENT */ - range_typelem = ANYELEMENTOID; + if (array_typeid == ANYARRAYOID && !have_anyelement) + { + /* Special case for ANYARRAY input: okay iff no ANYELEMENT */ + array_typelem = ANYELEMENTOID; + } + else + { + array_typelem = get_element_type(array_typeid); + if (!OidIsValid(array_typelem)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("argument declared %s is not an array but type %s", + "anyarray", format_type_be(array_typeid)))); + } + + if (!OidIsValid(elem_typeid)) + { + /* + * if we don't have an element type yet, use the one we just + * got + */ + elem_typeid = array_typelem; + } + else if (array_typelem != elem_typeid) + { + /* otherwise, they better match */ + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("argument declared %s is not consistent with argument declared %s", + "anyarray", "anyelement"), + errdetail("%s versus %s", + format_type_be(array_typeid), + format_type_be(elem_typeid)))); + } } - else + + /* Get the element type based on the range type, if we have one */ + if (OidIsValid(range_typeid)) { - range_typelem = get_range_subtype(range_typeid); - if (!OidIsValid(range_typelem)) + if (range_typeid == ANYRANGEOID && !have_anyelement) + { + /* Special case for ANYRANGE input: okay iff no ANYELEMENT */ + range_typelem = ANYELEMENTOID; + } + else + { + range_typelem = get_range_subtype(range_typeid); + if (!OidIsValid(range_typelem)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("argument declared %s is not a range type but type %s", + "anyrange", + format_type_be(range_typeid)))); + } + + if (!OidIsValid(elem_typeid)) + { + /* + * if we don't have an element type yet, use the one we just + * got + */ + elem_typeid = range_typelem; + } + else if (range_typelem != elem_typeid) + { + /* otherwise, they better match */ ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("argument declared %s is not a range type but type %s", - "anyrange", - format_type_be(range_typeid)))); + errmsg("argument declared %s is not consistent with argument declared %s", + "anyrange", "anyelement"), + errdetail("%s versus %s", + format_type_be(range_typeid), + format_type_be(elem_typeid)))); + } } if (!OidIsValid(elem_typeid)) { + if (allow_poly) + { + elem_typeid = ANYELEMENTOID; + array_typeid = ANYARRAYOID; + range_typeid = ANYRANGEOID; + } + else + { + /* Only way to get here is if all the generic args are UNKNOWN */ + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("could not determine polymorphic type because input has type %s", + "unknown"))); + } + } + + if (have_anynonarray && elem_typeid != ANYELEMENTOID) + { /* - * if we don't have an element type yet, use the one we just got + * require the element type to not be an array or domain over + * array */ - elem_typeid = range_typelem; + if (type_is_array_domain(elem_typeid)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("type matched to anynonarray is an array type: %s", + format_type_be(elem_typeid)))); } - else if (range_typelem != elem_typeid) + + if (have_anyenum && elem_typeid != ANYELEMENTOID) { - /* otherwise, they better match */ - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("argument declared %s is not consistent with argument declared %s", - "anyrange", "anyelement"), - errdetail("%s versus %s", - format_type_be(range_typeid), - format_type_be(elem_typeid)))); + /* require the element type to be an enum */ + if (!type_is_enum(elem_typeid)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("type matched to anyenum is not an enum type: %s", + format_type_be(elem_typeid)))); } } - if (!OidIsValid(elem_typeid)) + if (have_generics_anycompatible) { - if (allow_poly) + if (n_anycompatible_args > 0) { - elem_typeid = ANYELEMENTOID; - array_typeid = ANYARRAYOID; - range_typeid = ANYRANGEOID; + anycompatible_typeid = select_common_type_from_vector(n_anycompatible_args, + anycompatible_actual_types, + false); + + if (have_anycompatible_array) + { + anycompatible_array_typeid = get_array_type(anycompatible_typeid); + + if (!OidIsValid(anycompatible_array_typeid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find array type for data type %s", + format_type_be(anycompatible_typeid)))); + } + + /* anycompatible_range_typid should be defined already */ + /* XXX this error message is not very on-point */ + if (have_anycompatible_range && + !OidIsValid(anycompatible_range_typeid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find range type for data type %s", + "anycompatiblerange"))); + + if (have_anycompatible_nonarray) + { + /* + * require the element type to not be an array or domain over + * array + */ + if (type_is_array_domain(anycompatible_typeid)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("type matched to anynonarray is an array type: %s", + format_type_be(anycompatible_typeid)))); + } } else { - /* Only way to get here is if all the generic args are UNKNOWN */ - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("could not determine polymorphic type because input has type %s", - "unknown"))); + if (allow_poly) + { + anycompatible_typeid = ANYCOMPATIBLEOID; + anycompatible_array_typeid = ANYCOMPATIBLEARRAYOID; + anycompatible_range_typeid = ANYCOMPATIBLERANGEOID; + } + else + { + /* Only way to get here is if all the generic args are UNKNOWN */ + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("could not determine polymorphic common type because input has type %s", + "unknown"))); + } } - } - if (have_anynonarray && elem_typeid != ANYELEMENTOID) - { - /* require the element type to not be an array or domain over array */ - if (type_is_array_domain(elem_typeid)) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("type matched to anynonarray is an array type: %s", - format_type_be(elem_typeid)))); - } + /* replace polymorphic common types by selected common types */ + for (j = 0; j < nargs; j++) + { + Oid decl_type = declared_arg_types[j]; - if (have_anyenum && elem_typeid != ANYELEMENTOID) - { - /* require the element type to be an enum */ - if (!type_is_enum(elem_typeid)) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("type matched to anyenum is not an enum type: %s", - format_type_be(elem_typeid)))); + if (decl_type == ANYCOMPATIBLEOID || + decl_type == ANYCOMPATIBLENONARRAYOID) /* XXX seems wrong? */ + declared_arg_types[j] = anycompatible_typeid; + else if (decl_type == ANYCOMPATIBLEARRAYOID) + declared_arg_types[j] = anycompatible_array_typeid; + else if (decl_type == ANYCOMPATIBLERANGEOID) + declared_arg_types[j] = anycompatible_range_typeid; + } } /* @@ -1962,6 +2365,35 @@ enforce_generic_type_consistency(const Oid *actual_arg_types, rettype == ANYENUMOID) return elem_typeid; + if (rettype == ANYCOMPATIBLEOID) + { + if (!OidIsValid(anycompatible_typeid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find common type"))); + return anycompatible_typeid; + } + + if (rettype == ANYCOMPATIBLEARRAYOID) + { + if (!OidIsValid(anycompatible_array_typeid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find common array type"))); + return anycompatible_array_typeid; + } + + /* if we return ANYRANGE use the appropriate argument type */ + if (rettype == ANYCOMPATIBLERANGEOID) + { + if (!OidIsValid(anycompatible_range_typeid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find range type for data type %s", + "anycompatiblerange"))); + return anycompatible_range_typeid; + } + /* we don't return a generic type; send back the original return type */ return rettype; } @@ -1979,11 +2411,12 @@ check_valid_polymorphic_signature(Oid ret_type, const Oid *declared_arg_types, int nargs) { - if (ret_type == ANYRANGEOID) + if (ret_type == ANYRANGEOID || ret_type == ANYCOMPATIBLERANGEOID) { /* * ANYRANGE requires an ANYRANGE input, else we can't tell which of - * several range types with the same element type to use. + * several range types with the same element type to use. Likewise + * for ANYCOMPATIBLERANGE. */ for (int i = 0; i < nargs; i++) { @@ -1993,17 +2426,28 @@ check_valid_polymorphic_signature(Oid ret_type, return psprintf(_("A result of type %s requires at least one input of type %s."), format_type_be(ret_type), format_type_be(ret_type)); } - else if (IsPolymorphicType(ret_type)) + else if (IsPolymorphicTypeFamily1(ret_type)) { - /* Otherwise, any polymorphic type can be deduced from any other */ + /* Otherwise, any family-1 type can be deduced from any other */ for (int i = 0; i < nargs; i++) { - if (IsPolymorphicType(declared_arg_types[i])) + if (IsPolymorphicTypeFamily1(declared_arg_types[i])) return NULL; /* OK */ } return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange."), format_type_be(ret_type)); } + else if (IsPolymorphicTypeFamily2(ret_type)) + { + /* Otherwise, any family-2 type can be deduced from any other */ + for (int i = 0; i < nargs; i++) + { + if (IsPolymorphicTypeFamily2(declared_arg_types[i])) + return NULL; /* OK */ + } + return psprintf(_("A result of type %s requires at least one input of type anycompatible, anycompatiblearray, anycompatiblenonarray, or anycompatiblerange."), + format_type_be(ret_type)); + } else return NULL; /* OK, ret_type is not polymorphic */ } @@ -2108,8 +2552,9 @@ IsBinaryCoercible(Oid srctype, Oid targettype) if (srctype == targettype) return true; - /* Anything is coercible to ANY or ANYELEMENT */ - if (targettype == ANYOID || targettype == ANYELEMENTOID) + /* Anything is coercible to ANY or ANYELEMENT or ANYCOMPATIBLE */ + if (targettype == ANYOID || targettype == ANYELEMENTOID || + targettype == ANYCOMPATIBLEOID) return true; /* If srctype is a domain, reduce to its base type */ @@ -2120,13 +2565,13 @@ IsBinaryCoercible(Oid srctype, Oid targettype) if (srctype == targettype) return true; - /* Also accept any array type as coercible to ANYARRAY */ - if (targettype == ANYARRAYOID) + /* Also accept any array type as coercible to ANY[COMPATIBLE]ARRAY */ + if (targettype == ANYARRAYOID || targettype == ANYCOMPATIBLEARRAYOID) if (type_is_array(srctype)) return true; - /* Also accept any non-array type as coercible to ANYNONARRAY */ - if (targettype == ANYNONARRAYOID) + /* Also accept any non-array type as coercible to ANY[COMPATIBLE]NONARRAY */ + if (targettype == ANYNONARRAYOID || targettype == ANYCOMPATIBLENONARRAYOID) if (!type_is_array(srctype)) return true; @@ -2135,8 +2580,8 @@ IsBinaryCoercible(Oid srctype, Oid targettype) if (type_is_enum(srctype)) return true; - /* Also accept any range type as coercible to ANYRANGE */ - if (targettype == ANYRANGEOID) + /* Also accept any range type as coercible to ANY[COMPATIBLE]RANGE */ + if (targettype == ANYRANGEOID || targettype == ANYCOMPATIBLERANGEOID) if (type_is_range(srctype)) return true; diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index f78420e..8bb00ab 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -195,7 +195,7 @@ json_categorize_type(Oid typoid, default: /* Check for arrays and composites */ if (OidIsValid(get_element_type(typoid)) || typoid == ANYARRAYOID - || typoid == RECORDARRAYOID) + || typoid == ANYCOMPATIBLEARRAYOID || typoid == RECORDARRAYOID) *tcategory = JSONTYPE_ARRAY; else if (type_is_rowtype(typoid)) /* includes RECORDOID */ *tcategory = JSONTYPE_COMPOSITE; diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index b961d29..1e9ca04 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -677,7 +677,7 @@ jsonb_categorize_type(Oid typoid, default: /* Check for arrays and composites */ if (OidIsValid(get_element_type(typoid)) || typoid == ANYARRAYOID - || typoid == RECORDARRAYOID) + || typoid == ANYCOMPATIBLEARRAYOID || typoid == RECORDARRAYOID) *tcategory = JSONBTYPE_ARRAY; else if (type_is_rowtype(typoid)) /* includes RECORDOID */ *tcategory = JSONBTYPE_COMPOSITE; diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c index 9eee03c..3d6b2f9 100644 --- a/src/backend/utils/adt/pseudotypes.c +++ b/src/backend/utils/adt/pseudotypes.c @@ -169,6 +169,26 @@ anyarray_send(PG_FUNCTION_ARGS) } /* + * anycompatiblearray + * + * We may as well allow output, since we do for anyarray. + */ +PSEUDOTYPE_DUMMY_INPUT_FUNC(anycompatiblearray); +PSEUDOTYPE_DUMMY_RECEIVE_FUNC(anycompatiblearray); + +Datum +anycompatiblearray_out(PG_FUNCTION_ARGS) +{ + return array_out(fcinfo); +} + +Datum +anycompatiblearray_send(PG_FUNCTION_ARGS) +{ + return array_send(fcinfo); +} + +/* * anyenum * * We may as well allow output, since enum_out will in fact work. @@ -195,6 +215,19 @@ anyrange_out(PG_FUNCTION_ARGS) } /* + * anycompatiblerange + * + * We may as well allow output, since range_out will in fact work. + */ +PSEUDOTYPE_DUMMY_INPUT_FUNC(anycompatiblerange); + +Datum +anycompatiblerange_out(PG_FUNCTION_ARGS) +{ + return range_out(fcinfo); +} + +/* * void * * We support void_in so that PL functions can return VOID without any @@ -316,3 +349,5 @@ PSEUDOTYPE_DUMMY_IO_FUNCS(tsm_handler); PSEUDOTYPE_DUMMY_IO_FUNCS(internal); PSEUDOTYPE_DUMMY_IO_FUNCS(anyelement); PSEUDOTYPE_DUMMY_IO_FUNCS(anynonarray); +PSEUDOTYPE_DUMMY_IO_FUNCS(anycompatible); +PSEUDOTYPE_DUMMY_IO_FUNCS(anycompatiblenonarray); diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c index 4e9d21b..78ed857 100644 --- a/src/backend/utils/fmgr/funcapi.c +++ b/src/backend/utils/fmgr/funcapi.c @@ -552,7 +552,9 @@ resolve_anyrange_from_others(polymorphic_actuals *actuals) * with concrete data types deduced from the input arguments. * declared_args is an oidvector of the function's declared input arg types * (showing which are polymorphic), and call_expr is the call expression. - * Returns true if able to deduce all types, false if not. + * + * Returns true if able to deduce all types, false if necessary information + * is not provided (call_expr is NULL or arg types aren't identifiable). */ static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, @@ -564,8 +566,13 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, bool have_anyelement_result = false; bool have_anyarray_result = false; bool have_anyrange_result = false; + bool have_anycompatible_result = false; + bool have_anycompatible_array_result = false; + bool have_anycompatible_range_result = false; polymorphic_actuals poly_actuals; + polymorphic_actuals anyc_actuals; Oid anycollation = InvalidOid; + Oid anycompatcollation = InvalidOid; int i; /* See if there are any polymorphic outputs; quick out if not */ @@ -587,6 +594,19 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, have_polymorphic_result = true; have_anyrange_result = true; break; + case ANYCOMPATIBLEOID: + case ANYCOMPATIBLENONARRAYOID: + have_polymorphic_result = true; + have_anycompatible_result = true; + break; + case ANYCOMPATIBLEARRAYOID: + have_polymorphic_result = true; + have_anycompatible_array_result = true; + break; + case ANYCOMPATIBLERANGEOID: + have_polymorphic_result = true; + have_anycompatible_range_result = true; + break; default: break; } @@ -596,12 +616,16 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, /* * Otherwise, extract actual datatype(s) from input arguments. (We assume - * the parser already validated consistency of the arguments.) + * the parser already validated consistency of the arguments. Also, for + * the ANYCOMPATIBLE pseudotype family, we expect that all matching + * arguments were coerced to the selected common supertype, so that it + * doesn't matter which one's exposed type we look at.) */ if (!call_expr) return false; /* no hope */ memset(&poly_actuals, 0, sizeof(poly_actuals)); + memset(&anyc_actuals, 0, sizeof(anyc_actuals)); for (i = 0; i < nargs; i++) { @@ -636,6 +660,34 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, return false; } break; + case ANYCOMPATIBLEOID: + case ANYCOMPATIBLENONARRAYOID: + if (!OidIsValid(anyc_actuals.anyelement_type)) + { + anyc_actuals.anyelement_type = + get_call_expr_argtype(call_expr, i); + if (!OidIsValid(anyc_actuals.anyelement_type)) + return false; + } + break; + case ANYCOMPATIBLEARRAYOID: + if (!OidIsValid(anyc_actuals.anyarray_type)) + { + anyc_actuals.anyarray_type = + get_call_expr_argtype(call_expr, i); + if (!OidIsValid(anyc_actuals.anyarray_type)) + return false; + } + break; + case ANYCOMPATIBLERANGEOID: + if (!OidIsValid(anyc_actuals.anyrange_type)) + { + anyc_actuals.anyrange_type = + get_call_expr_argtype(call_expr, i); + if (!OidIsValid(anyc_actuals.anyrange_type)) + return false; + } + break; default: break; } @@ -651,18 +703,33 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type)) resolve_anyrange_from_others(&poly_actuals); + if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type)) + resolve_anyelement_from_others(&anyc_actuals); + + if (have_anycompatible_array_result && !OidIsValid(anyc_actuals.anyarray_type)) + resolve_anyarray_from_others(&anyc_actuals); + + if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type)) + resolve_anyrange_from_others(&anyc_actuals); + /* * Identify the collation to use for polymorphic OUT parameters. (It'll - * necessarily be the same for both anyelement and anyarray.) Note that - * range types are not collatable, so any possible internal collation of a - * range type is not considered here. + * necessarily be the same for both anyelement and anyarray, likewise for + * anycompatible and anycompatiblearray.) Note that range types are not + * collatable, so any possible internal collation of a range type is not + * considered here. */ if (OidIsValid(poly_actuals.anyelement_type)) anycollation = get_typcollation(poly_actuals.anyelement_type); else if (OidIsValid(poly_actuals.anyarray_type)) anycollation = get_typcollation(poly_actuals.anyarray_type); - if (OidIsValid(anycollation)) + if (OidIsValid(anyc_actuals.anyelement_type)) + anycompatcollation = get_typcollation(anyc_actuals.anyelement_type); + else if (OidIsValid(anyc_actuals.anyarray_type)) + anycompatcollation = get_typcollation(anyc_actuals.anyarray_type); + + if (OidIsValid(anycollation) || OidIsValid(anycompatcollation)) { /* * The types are collatable, so consider whether to use a nondefault @@ -672,7 +739,12 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, Oid inputcollation = exprInputCollation(call_expr); if (OidIsValid(inputcollation)) - anycollation = inputcollation; + { + if (OidIsValid(anycollation)) + anycollation = inputcollation; + if (OidIsValid(anycompatcollation)) + anycompatcollation = inputcollation; + } } /* And finally replace the tuple column types as needed */ @@ -708,6 +780,31 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, 0); /* no collation should be attached to a range type */ break; + case ANYCOMPATIBLEOID: + case ANYCOMPATIBLENONARRAYOID: + TupleDescInitEntry(tupdesc, i + 1, + NameStr(att->attname), + anyc_actuals.anyelement_type, + -1, + 0); + TupleDescInitEntryCollation(tupdesc, i + 1, anycompatcollation); + break; + case ANYCOMPATIBLEARRAYOID: + TupleDescInitEntry(tupdesc, i + 1, + NameStr(att->attname), + anyc_actuals.anyarray_type, + -1, + 0); + TupleDescInitEntryCollation(tupdesc, i + 1, anycompatcollation); + break; + case ANYCOMPATIBLERANGEOID: + TupleDescInitEntry(tupdesc, i + 1, + NameStr(att->attname), + anyc_actuals.anyrange_type, + -1, + 0); + /* no collation should be attached to a range type */ + break; default: break; } @@ -720,7 +817,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, * Given the declared argument types and modes for a function, replace any * polymorphic types (ANYELEMENT etc) in argtypes[] with concrete data types * deduced from the input arguments found in call_expr. - * Returns true if able to deduce all types, false if not. + * + * Returns true if able to deduce all types, false if necessary information + * is not provided (call_expr is NULL or arg types aren't identifiable). * * This is the same logic as resolve_polymorphic_tupdesc, but with a different * argument representation, and slightly different output responsibilities. @@ -735,16 +834,21 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, bool have_anyelement_result = false; bool have_anyarray_result = false; bool have_anyrange_result = false; + bool have_anycompatible_result = false; + bool have_anycompatible_array_result = false; + bool have_anycompatible_range_result = false; polymorphic_actuals poly_actuals; + polymorphic_actuals anyc_actuals; int inargno; int i; /* * First pass: resolve polymorphic inputs, check for outputs. As in * resolve_polymorphic_tupdesc, we rely on the parser to have enforced - * type consistency. + * type consistency and coerced ANYCOMPATIBLE args to a common supertype. */ memset(&poly_actuals, 0, sizeof(poly_actuals)); + memset(&anyc_actuals, 0, sizeof(anyc_actuals)); inargno = 0; for (i = 0; i < numargs; i++) { @@ -808,6 +912,61 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, argtypes[i] = poly_actuals.anyrange_type; } break; + case ANYCOMPATIBLEOID: + case ANYCOMPATIBLENONARRAYOID: + if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE) + { + have_polymorphic_result = true; + have_anycompatible_result = true; + } + else + { + if (!OidIsValid(anyc_actuals.anyelement_type)) + { + anyc_actuals.anyelement_type = + get_call_expr_argtype(call_expr, inargno); + if (!OidIsValid(anyc_actuals.anyelement_type)) + return false; + } + argtypes[i] = anyc_actuals.anyelement_type; + } + break; + case ANYCOMPATIBLEARRAYOID: + if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE) + { + have_polymorphic_result = true; + have_anycompatible_array_result = true; + } + else + { + if (!OidIsValid(anyc_actuals.anyarray_type)) + { + anyc_actuals.anyarray_type = + get_call_expr_argtype(call_expr, inargno); + if (!OidIsValid(anyc_actuals.anyarray_type)) + return false; + } + argtypes[i] = anyc_actuals.anyarray_type; + } + break; + case ANYCOMPATIBLERANGEOID: + if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE) + { + have_polymorphic_result = true; + have_anycompatible_range_result = true; + } + else + { + if (!OidIsValid(anyc_actuals.anyrange_type)) + { + anyc_actuals.anyrange_type = + get_call_expr_argtype(call_expr, inargno); + if (!OidIsValid(anyc_actuals.anyrange_type)) + return false; + } + argtypes[i] = anyc_actuals.anyrange_type; + } + break; default: break; } @@ -829,6 +988,15 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type)) resolve_anyrange_from_others(&poly_actuals); + if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type)) + resolve_anyelement_from_others(&anyc_actuals); + + if (have_anycompatible_array_result && !OidIsValid(anyc_actuals.anyarray_type)) + resolve_anyarray_from_others(&anyc_actuals); + + if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type)) + resolve_anyrange_from_others(&anyc_actuals); + /* And finally replace the output column types as needed */ for (i = 0; i < numargs; i++) { @@ -845,6 +1013,16 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, case ANYRANGEOID: argtypes[i] = poly_actuals.anyrange_type; break; + case ANYCOMPATIBLEOID: + case ANYCOMPATIBLENONARRAYOID: + argtypes[i] = anyc_actuals.anyelement_type; + break; + case ANYCOMPATIBLEARRAYOID: + argtypes[i] = anyc_actuals.anyarray_type; + break; + case ANYCOMPATIBLERANGEOID: + argtypes[i] = anyc_actuals.anyrange_type; + break; default: break; } diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 7fb574f..8387238 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -7106,6 +7106,42 @@ { oid => '268', descr => 'I/O', proname => 'table_am_handler_out', prorettype => 'cstring', proargtypes => 'table_am_handler', prosrc => 'table_am_handler_out' }, +{ oid => '9559', descr => 'I/O', + proname => 'anycompatible_in', prorettype => 'anycompatible', + proargtypes => 'cstring', prosrc => 'anycompatible_in' }, +{ oid => '9560', descr => 'I/O', + proname => 'anycompatible_out', prorettype => 'cstring', + proargtypes => 'anycompatible', prosrc => 'anycompatible_out' }, +{ oid => '9561', descr => 'I/O', + proname => 'anycompatiblearray_in', prorettype => 'anycompatiblearray', + proargtypes => 'cstring', prosrc => 'anycompatiblearray_in' }, +{ oid => '9562', descr => 'I/O', + proname => 'anycompatiblearray_out', provolatile => 's', + prorettype => 'cstring', proargtypes => 'anycompatiblearray', + prosrc => 'anycompatiblearray_out' }, +{ oid => '9563', descr => 'I/O', + proname => 'anycompatiblearray_recv', provolatile => 's', + prorettype => 'anycompatiblearray', proargtypes => 'internal', + prosrc => 'anycompatiblearray_recv' }, +{ oid => '9564', descr => 'I/O', + proname => 'anycompatiblearray_send', provolatile => 's', + prorettype => 'bytea', proargtypes => 'anycompatiblearray', + prosrc => 'anycompatiblearray_send' }, +{ oid => '9565', descr => 'I/O', + proname => 'anycompatiblenonarray_in', prorettype => 'anycompatiblenonarray', + proargtypes => 'cstring', prosrc => 'anycompatiblenonarray_in' }, +{ oid => '9566', descr => 'I/O', + proname => 'anycompatiblenonarray_out', prorettype => 'cstring', + proargtypes => 'anycompatiblenonarray', + prosrc => 'anycompatiblenonarray_out' }, +{ oid => '9567', descr => 'I/O', + proname => 'anycompatiblerange_in', provolatile => 's', + prorettype => 'anycompatiblerange', proargtypes => 'cstring oid int4', + prosrc => 'anycompatiblerange_in' }, +{ oid => '9568', descr => 'I/O', + proname => 'anycompatiblerange_out', provolatile => 's', + prorettype => 'cstring', proargtypes => 'anycompatiblerange', + prosrc => 'anycompatiblerange_out' }, # tablesample method handlers { oid => '3313', descr => 'BERNOULLI tablesample method handler', diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat index b00597d..20d5167 100644 --- a/src/include/catalog/pg_type.dat +++ b/src/include/catalog/pg_type.dat @@ -590,5 +590,30 @@ typname => 'anyrange', typlen => '-1', typbyval => 'f', typtype => 'p', typcategory => 'P', typinput => 'anyrange_in', typoutput => 'anyrange_out', typreceive => '-', typsend => '-', typalign => 'd', typstorage => 'x' }, +{ oid => '9550', + descr => 'pseudo-type representing a polymorphic common type', + typname => 'anycompatible', typlen => '4', typbyval => 't', typtype => 'p', + typcategory => 'P', typinput => 'anycompatible_in', + typoutput => 'anycompatible_out', typreceive => '-', typsend => '-', + typalign => 'i' }, +{ oid => '9551', + descr => 'pseudo-type representing an array of polymorphic common type elements', + typname => 'anycompatiblearray', typlen => '-1', typbyval => 'f', + typtype => 'p', typcategory => 'P', typinput => 'anycompatiblearray_in', + typoutput => 'anycompatiblearray_out', + typreceive => 'anycompatiblearray_recv', typsend => 'anycompatiblearray_send', + typalign => 'd', typstorage => 'x' }, +{ oid => '9552', + descr => 'pseudo-type representing a polymorphic common type that is not an array', + typname => 'anycompatiblenonarray', typlen => '4', typbyval => 't', + typtype => 'p', typcategory => 'P', typinput => 'anycompatiblenonarray_in', + typoutput => 'anycompatiblenonarray_out', typreceive => '-', typsend => '-', + typalign => 'i' }, +{ oid => '9553', + descr => 'pseudo-type representing a polymorphic common type that is a range', + typname => 'anycompatiblerange', typlen => '-1', typbyval => 'f', + typtype => 'p', typcategory => 'P', typinput => 'anycompatiblerange_in', + typoutput => 'anycompatiblerange_out', typreceive => '-', typsend => '-', + typalign => 'd', typstorage => 'x' }, ] diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h index 9789094..7b37562 100644 --- a/src/include/catalog/pg_type.h +++ b/src/include/catalog/pg_type.h @@ -295,12 +295,23 @@ typedef FormData_pg_type *Form_pg_type; /* Is a type OID a polymorphic pseudotype? (Beware of multiple evaluation) */ #define IsPolymorphicType(typid) \ + (IsPolymorphicTypeFamily1(typid) || \ + IsPolymorphicTypeFamily2(typid)) + +/* Code not part of polymorphic type resolution should not use these macros: */ +#define IsPolymorphicTypeFamily1(typid) \ ((typid) == ANYELEMENTOID || \ (typid) == ANYARRAYOID || \ (typid) == ANYNONARRAYOID || \ (typid) == ANYENUMOID || \ (typid) == ANYRANGEOID) +#define IsPolymorphicTypeFamily2(typid) \ + ((typid) == ANYCOMPATIBLEOID || \ + (typid) == ANYCOMPATIBLEARRAYOID || \ + (typid) == ANYCOMPATIBLENONARRAYOID || \ + (typid) == ANYCOMPATIBLERANGEOID) + #endif /* EXPOSE_TO_CLIENT_CODE */ diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index c8e43e6..828ff5a 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -507,11 +507,13 @@ do_compile(FunctionCallInfo fcinfo, { if (forValidator) { - if (rettypeid == ANYARRAYOID) + if (rettypeid == ANYARRAYOID || + rettypeid == ANYCOMPATIBLEARRAYOID) rettypeid = INT4ARRAYOID; - else if (rettypeid == ANYRANGEOID) + else if (rettypeid == ANYRANGEOID || + rettypeid == ANYCOMPATIBLERANGEOID) rettypeid = INT4RANGEOID; - else /* ANYELEMENT or ANYNONARRAY */ + else /* ANYELEMENT or ANYNONARRAY or ANYCOMPATIBLE */ rettypeid = INT4OID; /* XXX what could we use for ANYENUM? */ } @@ -2493,12 +2495,16 @@ plpgsql_resolve_polymorphic_argtypes(int numargs, case ANYELEMENTOID: case ANYNONARRAYOID: case ANYENUMOID: /* XXX dubious */ + case ANYCOMPATIBLEOID: + case ANYCOMPATIBLENONARRAYOID: argtypes[i] = INT4OID; break; case ANYARRAYOID: + case ANYCOMPATIBLEARRAYOID: argtypes[i] = INT4ARRAYOID; break; case ANYRANGEOID: + case ANYCOMPATIBLERANGEOID: argtypes[i] = INT4RANGEOID; break; default: diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out index 1573753..8057c20 100644 --- a/src/test/regress/expected/polymorphism.out +++ b/src/test/regress/expected/polymorphism.out @@ -1547,3 +1547,177 @@ View definition: drop view dfview; drop function dfunc(anyelement, anyelement, bool); +create or replace function cttestfunc01(anycompatible, anycompatible) +returns anycompatible as $$ +begin + if $1 > $2 then + return $1; + else + return $2; + end if; +end; +$$ language plpgsql; +create or replace function cttestfunc02(anycompatible, anycompatible) +returns anycompatiblearray as $$ +begin + return ARRAY[$1, $2]; +end; +$$ language plpgsql; +create or replace function cttestfunc03(anycompatiblearray) +returns anycompatible as $$ +begin + return $1[1]; +end; +$$ language plpgsql; +create or replace function cttestfunc04(variadic anycompatiblearray) +returns anycompatible as $$ +begin + return (select min(v) from unnest($1) g(v)); +end; +$$ language plpgsql; +create or replace function cttestfunc05(variadic anycompatiblearray) +returns anycompatiblearray as $$ +begin + return $1; +end; +$$ language plpgsql; +create or replace function cttestfunc06(anycompatiblenonarray, anycompatiblenonarray) +returns anycompatiblearray as $$ +begin + return ARRAY[$1, $2]; +end; +$$ language plpgsql; +create or replace function cttestfunc07(variadic anycompatiblearray) +returns anycompatiblearray as $$ + select $1 +$$ language sql; +create or replace function cttestfunc08(anycompatiblenonarray, anycompatiblenonarray) +returns anycompatiblearray as $$ +select array[$1, $2] +$$ language sql; +select cttestfunc01(10, 20); + cttestfunc01 +-------------- + 20 +(1 row) + +select cttestfunc01(10.1, 20.1); + cttestfunc01 +-------------- + 20.1 +(1 row) + +select cttestfunc01(10, 20.1); + cttestfunc01 +-------------- + 20.1 +(1 row) + +select cttestfunc02(10, 20); + cttestfunc02 +-------------- + {10,20} +(1 row) + +select cttestfunc02(10.1, 20.1); + cttestfunc02 +-------------- + {10.1,20.1} +(1 row) + +select cttestfunc02(10, 20.1); + cttestfunc02 +-------------- + {10,20.1} +(1 row) + +select cttestfunc03(ARRAY[10, 20]); + cttestfunc03 +-------------- + 10 +(1 row) + +select cttestfunc03(ARRAY[10.1, 20.1]); + cttestfunc03 +-------------- + 10.1 +(1 row) + +select cttestfunc03(ARRAY[10, 20.1]); + cttestfunc03 +-------------- + 10 +(1 row) + +select cttestfunc04(10, 20); + cttestfunc04 +-------------- + 10 +(1 row) + +select cttestfunc04(10.1, 20.1); + cttestfunc04 +-------------- + 10.1 +(1 row) + +select cttestfunc04(10, 20.1); + cttestfunc04 +-------------- + 10 +(1 row) + +select cttestfunc05(10, 20); + cttestfunc05 +-------------- + {10,20} +(1 row) + +select cttestfunc05(10.1, 20.1); + cttestfunc05 +-------------- + {10.1,20.1} +(1 row) + +select cttestfunc05(10, 20.1); + cttestfunc05 +-------------- + {10,20.1} +(1 row) + +select cttestfunc06(1,1.1); + cttestfunc06 +-------------- + {1,1.1} +(1 row) + +select cttestfunc07(10, 20); + cttestfunc07 +-------------- + {10,20} +(1 row) + +select cttestfunc07(10.1, 20.1); + cttestfunc07 +-------------- + {10.1,20.1} +(1 row) + +select cttestfunc07(10, 20.1); + cttestfunc07 +-------------- + {10,20.1} +(1 row) + +select cttestfunc08(1,1.1); + cttestfunc08 +-------------- + {1,1.1} +(1 row) + +-- should to fail +select cttestfunc06(array[10], array[2]); +ERROR: function cttestfunc06(integer[], integer[]) does not exist +LINE 1: select cttestfunc06(array[10], array[2]); + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. diff --git a/src/test/regress/sql/polymorphism.sql b/src/test/regress/sql/polymorphism.sql index 0360667..566b25b 100644 --- a/src/test/regress/sql/polymorphism.sql +++ b/src/test/regress/sql/polymorphism.sql @@ -814,3 +814,91 @@ select * from dfview; drop view dfview; drop function dfunc(anyelement, anyelement, bool); + +create or replace function cttestfunc01(anycompatible, anycompatible) +returns anycompatible as $$ +begin + if $1 > $2 then + return $1; + else + return $2; + end if; +end; +$$ language plpgsql; + +create or replace function cttestfunc02(anycompatible, anycompatible) +returns anycompatiblearray as $$ +begin + return ARRAY[$1, $2]; +end; +$$ language plpgsql; + +create or replace function cttestfunc03(anycompatiblearray) +returns anycompatible as $$ +begin + return $1[1]; +end; +$$ language plpgsql; + +create or replace function cttestfunc04(variadic anycompatiblearray) +returns anycompatible as $$ +begin + return (select min(v) from unnest($1) g(v)); +end; +$$ language plpgsql; + +create or replace function cttestfunc05(variadic anycompatiblearray) +returns anycompatiblearray as $$ +begin + return $1; +end; +$$ language plpgsql; + +create or replace function cttestfunc06(anycompatiblenonarray, anycompatiblenonarray) +returns anycompatiblearray as $$ +begin + return ARRAY[$1, $2]; +end; +$$ language plpgsql; + +create or replace function cttestfunc07(variadic anycompatiblearray) +returns anycompatiblearray as $$ + select $1 +$$ language sql; + +create or replace function cttestfunc08(anycompatiblenonarray, anycompatiblenonarray) +returns anycompatiblearray as $$ +select array[$1, $2] +$$ language sql; + + +select cttestfunc01(10, 20); +select cttestfunc01(10.1, 20.1); +select cttestfunc01(10, 20.1); + +select cttestfunc02(10, 20); +select cttestfunc02(10.1, 20.1); +select cttestfunc02(10, 20.1); + +select cttestfunc03(ARRAY[10, 20]); +select cttestfunc03(ARRAY[10.1, 20.1]); +select cttestfunc03(ARRAY[10, 20.1]); + +select cttestfunc04(10, 20); +select cttestfunc04(10.1, 20.1); +select cttestfunc04(10, 20.1); + +select cttestfunc05(10, 20); +select cttestfunc05(10.1, 20.1); +select cttestfunc05(10, 20.1); + +select cttestfunc06(1,1.1); + +select cttestfunc07(10, 20); +select cttestfunc07(10.1, 20.1); +select cttestfunc07(10, 20.1); + +select cttestfunc08(1,1.1); + +-- should to fail +select cttestfunc06(array[10], array[2]);