proposal: type info support functions for functions that use "any" type

Started by Pavel Stehulealmost 7 years ago20 messages
#1Pavel Stehule
pavel.stehule@gmail.com

Hi,

Tom introduced supported functions for calculation function's selectivity.
Still I have similar idea to use supported function for calculation
function's parameter's types and function return type.

Motivation:

Reduce a necessity of overloading of functions. My motivation is related
primary to Orafce, but this feature should be helpful for anybody with
similar goals. The function's overloading is great functionality but it is
hard for maintenance.

My idea to enhance a CREATE FUNCTION command to be able do

CREATE FUCNTION foo("any")
RETURNS "any" AS ...
TYPEINFO foo_typeinfo

CREATE FUNCTION decode(VARIADIC "any")
RETURNS "any" AS ...
TYPEINFO decode_typeinfo.

The typeinfo functions returns a pointer tu structure with param types and
result type. Only function with "any" parameters or "any" result can use
TYPEINFO supported function. This functionality should not be allowed for
common functions.

This functionality is limited just for C coders. But I expect so typical
application coder doesn't need it. It doesn't replace my proposal of
introduction other polymorphic type - now named "commontype" (can be named
differently). The commontype is good enough solution for application
coders, developers.

Comments, notes?

Regards

Pavel

#2Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#1)
1 attachment(s)
Re: proposal: type info support functions for functions that use "any" type

Hi

so 9. 3. 2019 v 7:22 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:

Hi,

Tom introduced supported functions for calculation function's selectivity.
Still I have similar idea to use supported function for calculation
function's parameter's types and function return type.

Motivation:

Reduce a necessity of overloading of functions. My motivation is related
primary to Orafce, but this feature should be helpful for anybody with
similar goals. The function's overloading is great functionality but it is
hard for maintenance.

My idea to enhance a CREATE FUNCTION command to be able do

CREATE FUCNTION foo("any")
RETURNS "any" AS ...
TYPEINFO foo_typeinfo

CREATE FUNCTION decode(VARIADIC "any")
RETURNS "any" AS ...
TYPEINFO decode_typeinfo.

The typeinfo functions returns a pointer tu structure with param types and
result type. Only function with "any" parameters or "any" result can use
TYPEINFO supported function. This functionality should not be allowed for
common functions.

This functionality is limited just for C coders. But I expect so typical
application coder doesn't need it. It doesn't replace my proposal of
introduction other polymorphic type - now named "commontype" (can be named
differently). The commontype is good enough solution for application
coders, developers.

Comments, notes?

here is a patch

I have not a plan to push decode function to upstream. Patch contains it
just as demo.

Regards

Pavel

Show quoted text

Regards

Pavel

Attachments:

parse_func-support-func.patchtext/x-patch; charset=US-ASCII; name=parse_func-support-func.patchDownload
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index cdd5006a72..79fce58cd9 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -1084,6 +1084,8 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
 				   effective_nargs * sizeof(Oid));
 		newResult->pathpos = pathpos;
 		newResult->oid = procform->oid;
+		newResult->support_func = InvalidOid;
+		newResult->rettype = procform->prorettype;
 		newResult->nargs = effective_nargs;
 		newResult->argnumbers = argnumbers;
 		if (argnumbers)
@@ -1100,6 +1102,7 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
 			/* Simple positional case, just copy proargtypes as-is */
 			memcpy(newResult->args, procform->proargtypes.values,
 				   pronargs * sizeof(Oid));
+
 		}
 		if (variadic)
 		{
@@ -1114,6 +1117,25 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
 			newResult->nvargs = 0;
 		newResult->ndargs = use_defaults ? pronargs - nargs : 0;
 
+		/*
+		 * When there are support function, we should to detect, if
+		 * there is any "any" argument, and if there is, then we should
+		 * to set support_tp_func_oid field.
+		 */
+		if (OidIsValid(procform->prosupport))
+		{
+			int			i;
+
+			for (i = 0; i < pronargs; i++)
+			{
+				if (newResult->args[i] == ANYOID)
+				{
+					newResult->support_func = procform->prosupport;
+					break;
+				}
+			}
+		}
+
 		/*
 		 * Does it have the same arguments as something we already accepted?
 		 * If so, decide what to do to avoid returning duplicate argument
@@ -1701,6 +1723,8 @@ OpernameGetCandidates(List *names, char oprkind, bool missing_schema_ok)
 
 		newResult->pathpos = pathpos;
 		newResult->oid = operform->oid;
+		newResult->support_func = InvalidOid;
+		newResult->rettype = operform->oprresult;
 		newResult->nargs = 2;
 		newResult->nvargs = 0;
 		newResult->ndargs = 0;
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 752cf1b315..46989604e3 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -22,6 +22,7 @@
 #include "lib/stringinfo.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "nodes/supportnodes.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
@@ -927,6 +928,36 @@ func_match_argtypes(int nargs,
 		 current_candidate = next_candidate)
 	{
 		next_candidate = current_candidate->next;
+
+		if (OidIsValid(current_candidate->support_func))
+		{
+			SupportRequestArgTypes		req;
+			SupportRequestArgTypes	   *result;
+
+			req.type = T_SupportRequestArgTypes;
+			req.candidate = current_candidate;
+			req.nargs = nargs;
+			req.typeids = input_typeids;
+
+			result = (SupportRequestArgTypes *)
+				DatumGetPointer(OidFunctionCall1(current_candidate->support_func,
+												 PointerGetDatum(&req)));
+
+			/*
+			 * Support function should not to support this request. Then
+			 * do nothing. Support function can detect failure, and returns
+			 * null as candidate. Then eliminate this candidate from list.
+			 */
+			if (result == &req)
+			{
+				if (result->candidate)
+					current_candidate = result->candidate;
+				else
+					/* skip this candidate */
+					continue;
+			}
+		}
+
 		if (can_coerce_type(nargs, input_typeids, current_candidate->args,
 							COERCION_IMPLICIT))
 		{
@@ -1589,6 +1620,7 @@ func_get_detail(List *funcname,
 
 		*funcid = best_candidate->oid;
 		*nvargs = best_candidate->nvargs;
+		*rettype = best_candidate->rettype;
 		*true_typeids = best_candidate->args;
 
 		/*
@@ -1617,7 +1649,6 @@ func_get_detail(List *funcname,
 			elog(ERROR, "cache lookup failed for function %u",
 				 best_candidate->oid);
 		pform = (Form_pg_proc) GETSTRUCT(ftup);
-		*rettype = pform->prorettype;
 		*retset = pform->proretset;
 		*vatype = pform->provariadic;
 		/* fetch default args if caller wants 'em */
diff --git a/src/backend/utils/adt/oracle_compat.c b/src/backend/utils/adt/oracle_compat.c
index 9d01e0be9c..18002e41d1 100644
--- a/src/backend/utils/adt/oracle_compat.c
+++ b/src/backend/utils/adt/oracle_compat.c
@@ -15,9 +15,16 @@
  */
 #include "postgres.h"
 
+#include "nodes/nodes.h"
+#include "nodes/supportnodes.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_type.h"
 #include "common/int.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_oper.h"
 #include "utils/builtins.h"
 #include "utils/formatting.h"
+#include "utils/lsyscache.h"
 #include "mb/pg_wchar.h"
 
 
@@ -1066,3 +1073,337 @@ repeat(PG_FUNCTION_ARGS)
 
 	PG_RETURN_TEXT_P(result);
 }
+
+typedef struct decode_fmgr_cache
+{
+	Oid			typid;
+	FmgrInfo	eqop_finfo;
+} decode_fmgr_cache;
+
+/*
+ * Decode function
+ */
+Datum
+decode(PG_FUNCTION_ARGS)
+{
+	decode_fmgr_cache *fmgr_cache;
+	Oid		search_oid;
+	Oid		result_oid;
+	Oid		rettype;
+	Oid		collation;
+	int			i;
+	int			result_narg;
+	int			nargs = PG_NARGS();
+	bool		expr_isnull;
+
+	if (nargs < 3)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("too few function arguments"),
+				 errhint("The decode function requires at least 3 arguments")));
+
+	search_oid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	result_oid = get_fn_expr_argtype(fcinfo->flinfo, 2);
+	collation = PG_GET_COLLATION();
+
+	if (fcinfo->flinfo->fn_extra == NULL)
+	{
+		MemoryContext		oldcxt;
+		Oid			eqop;
+
+		get_sort_group_operators(search_oid, false, true, false, NULL, &eqop, NULL, NULL);
+
+		oldcxt = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt);
+
+		fmgr_cache = palloc(sizeof(decode_fmgr_cache));
+		fmgr_cache->typid = search_oid;
+		fmgr_info(get_opcode(eqop), &fmgr_cache->eqop_finfo);
+
+		MemoryContextSwitchTo(oldcxt);
+
+		fcinfo->flinfo->fn_extra = fmgr_cache;
+	}
+	else
+		fmgr_cache = fcinfo->flinfo->fn_extra;
+
+	/* recheck of fmgr_cache validity, should not be */
+	if (fmgr_cache->typid != search_oid)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("search expression has unexpected type"),
+				 errhint("The decode expects \"%s\" type",
+							format_type_be(search_oid))));
+
+	/* recheck rerttype, should not be */
+	rettype = get_fn_expr_rettype(fcinfo->flinfo);
+	if (rettype != result_oid)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("result expression has unexpected type"),
+				 errhint("The decode expects \"%s\" type",
+							format_type_be(result_oid))));
+
+	result_narg = nargs % 2 ? -1 : nargs - 1;
+	expr_isnull = PG_ARGISNULL(0);
+
+	for (i = 1; i < nargs; i += 2)
+	{
+		if (expr_isnull)
+		{
+			if (PG_ARGISNULL(i) && i + 1 < nargs)
+			{
+				result_narg = i + 1;
+				break;
+			}
+		}
+		else
+		{
+			if (!PG_ARGISNULL(i) && i + 1 < nargs)
+			{
+				Datum		result;
+
+				/* recheck type */
+				if (get_fn_expr_argtype(fcinfo->flinfo, i) != search_oid)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("search expression has unexpected type"),
+							 errhint("The decode expects \"%s\" type",
+										format_type_be(search_oid))));
+
+				result = FunctionCall2Coll(&fmgr_cache->eqop_finfo,
+										   collation,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(i));
+
+				if (DatumGetBool(result))
+				{
+					result_narg = i + 1;
+					break;
+				}
+			}
+		}
+	}
+
+	if (result_narg >= 0 && !PG_ARGISNULL(result_narg))
+	{
+		if (get_fn_expr_argtype(fcinfo->flinfo, result_narg) != result_oid)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("result expression has unexpected type"),
+					 errhint("The decode expects \"%s\" type",
+								format_type_be(result_oid))));
+
+		PG_RETURN_DATUM(PG_GETARG_DATUM(result_narg));
+	}
+	else
+		PG_RETURN_NULL();
+}
+
+/*
+ * 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, lc
+	 * points to the first list item with type different from pexpr's; we need
+	 * not re-examine any items the previous loop advanced over.
+	 */
+	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;
+}
+
+/*
+ * Support function for decode function
+ *
+ * It converts VARIADIC "any" to real types, and set real expected type.
+ */
+Datum
+decode_support(PG_FUNCTION_ARGS)
+{
+	Node *rawreq = (Node *) PG_GETARG_POINTER(0);
+	Node *ret = NULL;
+
+	if (IsA(rawreq, SupportRequestArgTypes))
+	{
+		SupportRequestArgTypes *fd = (SupportRequestArgTypes *) rawreq;
+		FuncCandidateList candidate = fd->candidate;
+
+		if (candidate->nargs >= 3 && !candidate->argnumbers && fd->nargs == candidate->nargs)
+		{
+			FuncCandidateList newc;
+			Oid		search_oid;
+			Oid		result_oid;
+			Oid		search_typids[FUNC_MAX_ARGS];
+			Oid		result_typids[FUNC_MAX_ARGS];
+			int		search_nargs = 0;
+			int		result_nargs = 0;
+			int		i;
+
+			newc = palloc(offsetof(struct _FuncCandidateList, args) +
+							candidate->nargs * sizeof(Oid));
+
+			/*
+			 * Oracle should not to find most common types for numeric types, because
+			 * type number is generic and widely used as integer type too. Using same
+			 * setup is too simple for Postgres - using numeric instead int can has
+			 * negative performance impact.
+			 *
+			 * When type is not TEXT, then the most common type is used.
+			 */
+			search_oid = fd->typeids[1] != UNKNOWNOID ? fd->typeids[1] : TEXTOID;
+			result_oid = fd->typeids[2] != UNKNOWNOID ? fd->typeids[2] : TEXTOID;
+
+			if (search_oid != TEXTOID || result_oid != TEXTOID)
+			{
+				for (i = 0; i < fd->nargs; i++)
+				{
+					if (i == 0)
+						search_typids[search_nargs++] = fd->typeids[i];
+					else if (i % 2) /* even position */
+					{
+						if (i + 1 < fd->nargs)
+							search_typids[search_nargs++] = fd->typeids[i];
+						else
+							result_typids[result_nargs++] = fd->typeids[i];
+					}
+					else /* odd position */
+						result_typids[result_nargs++] = fd->typeids[i];
+				}
+
+				if (search_oid != TEXTOID)
+				{
+					search_oid = select_common_type_from_vector(search_nargs,
+																search_typids,
+																true);
+
+					if (!OidIsValid(search_oid)) /* should not to be */
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+								 errmsg("cannot to detect common type for search expression")));
+				}
+
+				if (result_oid != TEXTOID)
+				{
+					result_oid = select_common_type_from_vector(result_nargs,
+																result_typids,
+																true);
+
+					if (!OidIsValid(result_oid)) /* should not to be */
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+								 errmsg("cannot to detect common type for result expression")));
+				}
+			}
+
+			memcpy(newc, candidate, sizeof(struct _FuncCandidateList));
+
+			newc->rettype = result_oid;
+
+			/* expression is casted to search */
+			newc->args[0] = search_oid;
+
+			for (i = 1; i < candidate->nargs; i += 2)
+			{
+				/* is pair or alone default? */
+				if (i + 1 < candidate->nargs)
+				{
+					newc->args[i] = search_oid;
+					newc->args[i+1] = result_oid;
+				}
+				else
+					newc->args[i] = result_oid;
+			}
+
+			candidate = newc;
+		}
+		else
+			candidate = NULL;
+
+		fd->candidate = candidate;
+
+		ret = (Node *) fd;
+	}
+
+	PG_RETURN_POINTER(ret);
+}
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index 67418346e6..9a52a8260b 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -30,6 +30,8 @@ typedef struct _FuncCandidateList
 	struct _FuncCandidateList *next;
 	int			pathpos;		/* for internal use of namespace lookup */
 	Oid			oid;			/* the function or operator's OID */
+	Oid			support_func;	/* oid of support function if exists */
+	Oid			rettype;		/* return type set by support function */
 	int			nargs;			/* number of arg types returned */
 	int			nvargs;			/* number of args to become variadic array */
 	int			ndargs;			/* number of defaulted args */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index eac909109c..02fb56ef24 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3318,6 +3318,15 @@
   proname => 'repeat', prorettype => 'text', proargtypes => 'text int4',
   prosrc => 'repeat' },
 
+{ oid => '6107', descr => 'choose a value from list',
+  proname => 'decode', provariadic => 'any', proisstrict => 'f',
+  prosupport => 'decode_support', prorettype => 'any', proargtypes => 'any',
+  prosrc => 'decode',
+  proallargtypes => '{any}', proargmodes => '{v}'},
+{ oid => '6108', descr => 'parser support for decode',
+  proname => 'decode_support', prorettype => 'internal',
+  proargtypes => 'internal', prosrc => 'decode_support' },
+
 { oid => '1623', descr => 'convert SQL99 regexp pattern to POSIX style',
   proname => 'similar_escape', proisstrict => 'f', prorettype => 'text',
   proargtypes => 'text text', prosrc => 'similar_escape' },
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index ffb4cd4bcc..daee08c25d 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -512,7 +512,8 @@ typedef enum NodeTag
 	T_SupportRequestSelectivity,	/* in nodes/supportnodes.h */
 	T_SupportRequestCost,		/* in nodes/supportnodes.h */
 	T_SupportRequestRows,		/* in nodes/supportnodes.h */
-	T_SupportRequestIndexCondition	/* in nodes/supportnodes.h */
+	T_SupportRequestIndexCondition,	/* in nodes/supportnodes.h */
+	T_SupportRequestArgTypes	/* in nodes/supportnodes.h */
 } NodeTag;
 
 /*
diff --git a/src/include/nodes/supportnodes.h b/src/include/nodes/supportnodes.h
index 460d75bd2d..7a083e5241 100644
--- a/src/include/nodes/supportnodes.h
+++ b/src/include/nodes/supportnodes.h
@@ -38,7 +38,7 @@
 struct PlannerInfo;				/* avoid including pathnodes.h here */
 struct IndexOptInfo;
 struct SpecialJoinInfo;
-
+struct _FuncCandidateList;
 
 /*
  * The Simplify request allows the support function to perform plan-time
@@ -239,4 +239,26 @@ typedef struct SupportRequestIndexCondition
 								 * equivalent of the function call */
 } SupportRequestIndexCondition;
 
+/*
+ * PostgreSQL function's parameters are described by list of their types.
+ * For some specific functions this technique is not practical (require
+ * lot of overloaded functions), and better is using some algoritm. This
+ * node is used for functions that returns "any" type or has some "any"
+ * parameter.
+ *
+ * Support function is required for functions that returns "any" type.
+ * For "any" parameters it is optional, but it can increase a execution,
+ * because type detection (using exprType()) can be processed in parse
+ * time.
+ */
+typedef struct SupportRequestArgTypes
+{
+	NodeTag		type;
+
+	int			nargs;
+	Oid		   *typeids;
+	Oid			rettype;
+	struct _FuncCandidateList *candidate;
+} SupportRequestArgTypes;
+
 #endif							/* SUPPORTNODES_H */
#3Tom Lane
tgl@sss.pgh.pa.us
In reply to: Pavel Stehule (#2)
Re: proposal: type info support functions for functions that use "any" type

Pavel Stehule <pavel.stehule@gmail.com> writes:

so 9. 3. 2019 v 7:22 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:

Tom introduced supported functions for calculation function's selectivity.
Still I have similar idea to use supported function for calculation
function's parameter's types and function return type.
Motivation:
Reduce a necessity of overloading of functions. My motivation is related
primary to Orafce, but this feature should be helpful for anybody with
similar goals. The function's overloading is great functionality but it is
hard for maintenance.

here is a patch

TBH, I don't like this proposal one bit. As far as I can see, the idea
is to let a function's support function redefine the function's declared
argument and result types on-the-fly according to no predetermined rules,
and that seems to me like it's a recipe for disaster. How will anyone
understand which function(s) are candidates to match a query, or why one
particular candidate got selected over others? It's already hard enough
to understand the behavior of polymorphic functions in complex cases,
and those are much more constrained than this would be.

Moreover, I don't think you've even provided a compelling example
case. What's this doing that you couldn't do with existing polymorphic
types or the anycompatibletype proposal?

I also strongly suspect that this would break pieces of the system
that expect that the stored pg_proc.prorettype has something to do
with reality. At minimum, you'd need to fix a number of places you
haven't touched here that have their own knowledge of function type
resolution, such as enforce_generic_type_consistency,
resolve_polymorphic_argtypes, resolve_aggregate_transtype. Probably
anyplace that treats polymorphics as being any sort of special case
would have to be taught to re-call the support function to find out
what it should think the relevant types are.

(I don't even want to think about what happens if the support function's
behavior changes between original parsing and these re-checking spots.)

Another thing that's very much less than compelling about your example
is that your support function seems to be happy to throw errors
if the argument types don't match what it's expecting. That seems
quite unacceptable, since it would prevent the parser from moving on
to consider other possibly-matching functions. Maybe that's just
because it's a quick hack not a polished example, but it doesn't
seem like a good precedent.

In short, I think the added complexity and bug potential outweigh
any possible gain from this.

regards, tom lane

#4Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#3)
Re: proposal: type info support functions for functions that use "any" type

I wrote:

TBH, I don't like this proposal one bit. As far as I can see, the idea
is to let a function's support function redefine the function's declared
argument and result types on-the-fly according to no predetermined rules,
and that seems to me like it's a recipe for disaster. How will anyone
understand which function(s) are candidates to match a query, or why one
particular candidate got selected over others? It's already hard enough
to understand the behavior of polymorphic functions in complex cases,
and those are much more constrained than this would be.

After thinking about this a bit more, it seems like you could avoid
a lot of problems if you restricted what the support function call
does to be potentially replacing the result type of a function
declared to return ANY with some more-specific type (computed from
examination of the actual arguments). That would make it act much
more like a traditional polymorphic function. It'd remove the issues
about interactions among multiple potentially-matching functions,
since we'd only call a single support function for an already-identified
target function.

You'd still need to touch everyplace that knows about polymorphic
type resolution, since this would essentially be another form of
polymorphic function. And I'm still very dubious that it's worth
the trouble. But it would be a lot more controllable than the
proposal as it stands.

regards, tom lane

#5Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#3)
Re: proposal: type info support functions for functions that use "any" type

pá 26. 7. 2019 v 22:03 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:

Pavel Stehule <pavel.stehule@gmail.com> writes:

so 9. 3. 2019 v 7:22 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:

Tom introduced supported functions for calculation function's

selectivity.

Still I have similar idea to use supported function for calculation
function's parameter's types and function return type.
Motivation:
Reduce a necessity of overloading of functions. My motivation is related
primary to Orafce, but this feature should be helpful for anybody with
similar goals. The function's overloading is great functionality but it

is

hard for maintenance.

here is a patch

TBH, I don't like this proposal one bit. As far as I can see, the idea
is to let a function's support function redefine the function's declared
argument and result types on-the-fly according to no predetermined rules,
and that seems to me like it's a recipe for disaster. How will anyone
understand which function(s) are candidates to match a query, or why one
particular candidate got selected over others? It's already hard enough
to understand the behavior of polymorphic functions in complex cases,
and those are much more constrained than this would be.

I quietly expect so this feature will be used without combination with
overloading. But the combination of support function and overloading can be
explicitly disabled - (in runtime for simple implementation).

Moreover, I don't think you've even provided a compelling example
case. What's this doing that you couldn't do with existing polymorphic
types or the anycompatibletype proposal?

There are two cases of usage

a) combination of polymorphic types - fx(t1, t1, t2, t1, t2, t1, t2, ...)
b) forcing types fx(t1, t2) t1 force explicit cast for t2 to t1
c) optimization of repeated call of functions like fx("any", "any", "any",
...)

It is pretty hard to create simple non-procedural language to describe
syntaxes like @a. But with procedural code it is easy.

@c is special case, that we can do already. But we cannot to push casting
outside function, and inside function, there is a overhead with casting.
With implementing type case inside function, then we increase startup time
and it is overhead for function started by plpgsql runtime.

I also strongly suspect that this would break pieces of the system
that expect that the stored pg_proc.prorettype has something to do
with reality. At minimum, you'd need to fix a number of places you
haven't touched here that have their own knowledge of function type
resolution, such as enforce_generic_type_consistency,
resolve_polymorphic_argtypes, resolve_aggregate_transtype. Probably
anyplace that treats polymorphics as being any sort of special case
would have to be taught to re-call the support function to find out
what it should think the relevant types are.

(I don't even want to think about what happens if the support function's
behavior changes between original parsing and these re-checking spots.)

The helper function should be immutable - what I know, is not possible to
change data types dynamically, so repeated call should not be effective,
but should to produce same result, so it should not be a problem.

Another thing that's very much less than compelling about your example
is that your support function seems to be happy to throw errors
if the argument types don't match what it's expecting. That seems
quite unacceptable, since it would prevent the parser from moving on
to consider other possibly-matching functions. Maybe that's just
because it's a quick hack not a polished example, but it doesn't
seem like a good precedent.

In this case it is decision, because I don't expect overloading.

I understand to your objections about mixing parser helper functions and
overloading. Currently it is pretty hard to understand what will be
expected behave when somebody overload function with polymorphic function.

With parser helper function the overloading is not necessary and can be
disabled.

Show quoted text

In short, I think the added complexity and bug potential outweigh
any possible gain from this.

regards, tom lane

#6Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#4)
Re: proposal: type info support functions for functions that use "any" type

pá 26. 7. 2019 v 22:53 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:

I wrote:

TBH, I don't like this proposal one bit. As far as I can see, the idea
is to let a function's support function redefine the function's declared
argument and result types on-the-fly according to no predetermined rules,
and that seems to me like it's a recipe for disaster. How will anyone
understand which function(s) are candidates to match a query, or why one
particular candidate got selected over others? It's already hard enough
to understand the behavior of polymorphic functions in complex cases,
and those are much more constrained than this would be.

After thinking about this a bit more, it seems like you could avoid
a lot of problems if you restricted what the support function call
does to be potentially replacing the result type of a function
declared to return ANY with some more-specific type (computed from
examination of the actual arguments). That would make it act much
more like a traditional polymorphic function. It'd remove the issues
about interactions among multiple potentially-matching functions,
since we'd only call a single support function for an already-identified
target function.

I am not sure if I understand well - so I repeat it with my words.

So calculation of result type (replace ANY by some specific) can be ok?

I am able to do it if there will be a agreement.

I wrote a possibility to specify argument types as optimization as
protection against repeated type identification and casting (that can be
done in planning time, and should not be repeated).

This feature should be used only for functions with types fx("any", "any",
..) returns "any". So it is very probable so in execution type you should
to do some work with parameter type identification.

But if we find a agreement just on work with return type, then it is good
enough solution. The practical overhead of type cache inside function
should not be dramatic.

You'd still need to touch everyplace that knows about polymorphic
type resolution, since this would essentially be another form of
polymorphic function. And I'm still very dubious that it's worth
the trouble. But it would be a lot more controllable than the
proposal as it stands.

ok

Show quoted text

regards, tom lane

#7Thomas Munro
thomas.munro@gmail.com
In reply to: Pavel Stehule (#6)
Re: proposal: type info support functions for functions that use "any" type

On Sat, Jul 27, 2019 at 5:45 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:

pá 26. 7. 2019 v 22:53 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:

I wrote:

TBH, I don't like this proposal one bit. As far as I can see, the idea
is to let a function's support function redefine the function's declared
argument and result types on-the-fly according to no predetermined rules,
and that seems to me like it's a recipe for disaster. How will anyone
understand which function(s) are candidates to match a query, or why one
particular candidate got selected over others? It's already hard enough
to understand the behavior of polymorphic functions in complex cases,
and those are much more constrained than this would be.

After thinking about this a bit more, it seems like you could avoid
a lot of problems if you restricted what the support function call
does to be potentially replacing the result type of a function
declared to return ANY with some more-specific type (computed from
examination of the actual arguments). That would make it act much
more like a traditional polymorphic function. It'd remove the issues
about interactions among multiple potentially-matching functions,
since we'd only call a single support function for an already-identified
target function.

I am not sure if I understand well - so I repeat it with my words.

So calculation of result type (replace ANY by some specific) can be ok?

I am able to do it if there will be a agreement.

...

Hi Pavel,

I see that this is an active project with an ongoing discussion, but
we have run out of July so I have moved this to the September CF and
set it to "Waiting on Author".

--
Thomas Munro
https://enterprisedb.com

#8Pavel Stehule
pavel.stehule@gmail.com
In reply to: Thomas Munro (#7)
Re: proposal: type info support functions for functions that use "any" type

čt 1. 8. 2019 v 11:01 odesílatel Thomas Munro <thomas.munro@gmail.com>
napsal:

On Sat, Jul 27, 2019 at 5:45 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:

pá 26. 7. 2019 v 22:53 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:

I wrote:

TBH, I don't like this proposal one bit. As far as I can see, the

idea

is to let a function's support function redefine the function's

declared

argument and result types on-the-fly according to no predetermined

rules,

and that seems to me like it's a recipe for disaster. How will anyone
understand which function(s) are candidates to match a query, or why

one

particular candidate got selected over others? It's already hard

enough

to understand the behavior of polymorphic functions in complex cases,
and those are much more constrained than this would be.

After thinking about this a bit more, it seems like you could avoid
a lot of problems if you restricted what the support function call
does to be potentially replacing the result type of a function
declared to return ANY with some more-specific type (computed from
examination of the actual arguments). That would make it act much
more like a traditional polymorphic function. It'd remove the issues
about interactions among multiple potentially-matching functions,
since we'd only call a single support function for an already-identified
target function.

I am not sure if I understand well - so I repeat it with my words.

So calculation of result type (replace ANY by some specific) can be ok?

I am able to do it if there will be a agreement.

...

Hi Pavel,

I see that this is an active project with an ongoing discussion, but
we have run out of July so I have moved this to the September CF and
set it to "Waiting on Author".

sure

Pavel

Show quoted text

--
Thomas Munro
https://enterprisedb.com

#9Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#4)
2 attachment(s)
Re: proposal: type info support functions for functions that use "any" type

Hi

pá 26. 7. 2019 v 22:53 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:

I wrote:

TBH, I don't like this proposal one bit. As far as I can see, the idea
is to let a function's support function redefine the function's declared
argument and result types on-the-fly according to no predetermined rules,
and that seems to me like it's a recipe for disaster. How will anyone
understand which function(s) are candidates to match a query, or why one
particular candidate got selected over others? It's already hard enough
to understand the behavior of polymorphic functions in complex cases,
and those are much more constrained than this would be.

After thinking about this a bit more, it seems like you could avoid
a lot of problems if you restricted what the support function call
does to be potentially replacing the result type of a function
declared to return ANY with some more-specific type (computed from
examination of the actual arguments). That would make it act much
more like a traditional polymorphic function. It'd remove the issues
about interactions among multiple potentially-matching functions,
since we'd only call a single support function for an already-identified
target function.

You'd still need to touch everyplace that knows about polymorphic
type resolution, since this would essentially be another form of
polymorphic function. And I'm still very dubious that it's worth
the trouble. But it would be a lot more controllable than the
proposal as it stands.

I am sending reduced version of previous patch. Now, support function is
used just for replacement of returned type "any" by some other.

The are two patches - shorter with only support function, larger with demo
"decode" function. I don't expect so the "decode" extension should be
pushed to master. It is just demo of usage.

Regards

Pavel

Show quoted text

regards, tom lane

Attachments:

parser-support-function.patchtext/x-patch; charset=US-ASCII; name=parser-support-function.patchDownload
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 201242e796..640c506dd5 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -845,6 +845,7 @@ lookup_agg_function(List *fnName,
 	int			nvargs;
 	Oid			vatype;
 	Oid		   *true_oid_array;
+	Oid			support_func;
 	FuncDetailCode fdresult;
 	AclResult	aclresult;
 	int			i;
@@ -860,7 +861,8 @@ lookup_agg_function(List *fnName,
 							   nargs, input_types, false, false,
 							   &fnOid, rettype, &retset,
 							   &nvargs, &vatype,
-							   &true_oid_array, NULL);
+							   &true_oid_array, NULL,
+							   &support_func);
 
 	/* only valid case is a normal function not returning a set */
 	if (fdresult != FUNCDETAIL_NORMAL || !OidIsValid(fnOid))
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 8e926539e6..5974744668 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -22,6 +22,7 @@
 #include "lib/stringinfo.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "nodes/supportnodes.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
@@ -111,6 +112,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 	bool		retset;
 	int			nvargs;
 	Oid			vatype;
+	Oid			support_func;
 	FuncDetailCode fdresult;
 	char		aggkind = 0;
 	ParseCallbackState pcbstate;
@@ -265,7 +267,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 							   !func_variadic, true,
 							   &funcid, &rettype, &retset,
 							   &nvargs, &vatype,
-							   &declared_arg_types, &argdefaults);
+							   &declared_arg_types, &argdefaults,
+							   &support_func);
 
 	cancel_parser_errposition_callback(&pcbstate);
 
@@ -666,6 +669,31 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 	/* perform the necessary typecasting of arguments */
 	make_fn_arguments(pstate, fargs, actual_arg_types, declared_arg_types);
 
+	/*
+	 * When rettype is ANYOID we can call support function SupportRequestRettype if
+	 * it is available to get real type.
+	 */
+	if (rettype == ANYOID && OidIsValid(support_func))
+	{
+		SupportRequestRettype		req;
+		SupportRequestRettype	   *result;
+
+		req.type = T_SupportRequestRettype;
+		req.funcname = funcname;
+		req.fargs = fargs;
+		req.actual_arg_types = actual_arg_types;
+		req.declared_arg_types = declared_arg_types;
+		req.nargs = nargsplusdefs;
+
+		result = (SupportRequestRettype *)
+					DatumGetPointer(OidFunctionCall1(support_func,
+													 PointerGetDatum(&req)));
+
+		/* use result when it is valid */
+		if (result == &req)
+			rettype = result->rettype;
+	}
+
 	/*
 	 * If the function isn't actually variadic, forget any VARIADIC decoration
 	 * on the call.  (Perhaps we should throw an error instead, but
@@ -1392,7 +1420,8 @@ func_get_detail(List *funcname,
 				int *nvargs,	/* return value */
 				Oid *vatype,	/* return value */
 				Oid **true_typeids, /* return value */
-				List **argdefaults) /* optional return value */
+				List **argdefaults, /* optional return value */
+				Oid *support_func) /* return value */
 {
 	FuncCandidateList raw_candidates;
 	FuncCandidateList best_candidate;
@@ -1407,6 +1436,7 @@ func_get_detail(List *funcname,
 	*nvargs = 0;
 	*vatype = InvalidOid;
 	*true_typeids = NULL;
+	*support_func = InvalidOid;
 	if (argdefaults)
 		*argdefaults = NIL;
 
@@ -1519,6 +1549,7 @@ func_get_detail(List *funcname,
 					*nvargs = 0;
 					*vatype = InvalidOid;
 					*true_typeids = argtypes;
+					*support_func = InvalidOid;
 					return FUNCDETAIL_COERCION;
 				}
 			}
@@ -1616,6 +1647,7 @@ func_get_detail(List *funcname,
 		*rettype = pform->prorettype;
 		*retset = pform->proretset;
 		*vatype = pform->provariadic;
+		*support_func = pform->prosupport;
 		/* fetch default args if caller wants 'em */
 		if (argdefaults && best_candidate->ndargs > 0)
 		{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 13d5d542f9..a406f9c699 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10868,6 +10868,7 @@ generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
 	int			p_nvargs;
 	Oid			p_vatype;
 	Oid		   *p_true_typeids;
+	Oid			p_support_func;
 	bool		force_qualify = false;
 
 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
@@ -10922,7 +10923,8 @@ generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
 								   !use_variadic, true,
 								   &p_funcid, &p_rettype,
 								   &p_retset, &p_nvargs, &p_vatype,
-								   &p_true_typeids, NULL);
+								   &p_true_typeids, NULL,
+								   &p_support_func);
 	else
 	{
 		p_result = FUNCDETAIL_NOTFOUND;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 4e2fb39105..e2fb1cb83a 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -512,7 +512,8 @@ typedef enum NodeTag
 	T_SupportRequestSelectivity,	/* in nodes/supportnodes.h */
 	T_SupportRequestCost,		/* in nodes/supportnodes.h */
 	T_SupportRequestRows,		/* in nodes/supportnodes.h */
-	T_SupportRequestIndexCondition	/* in nodes/supportnodes.h */
+	T_SupportRequestIndexCondition,	/* in nodes/supportnodes.h */
+	T_SupportRequestRettype		/* in nodes/supportnodes.h */
 } NodeTag;
 
 /*
diff --git a/src/include/nodes/supportnodes.h b/src/include/nodes/supportnodes.h
index 460d75bd2d..abaf42cf96 100644
--- a/src/include/nodes/supportnodes.h
+++ b/src/include/nodes/supportnodes.h
@@ -239,4 +239,25 @@ typedef struct SupportRequestIndexCondition
 								 * equivalent of the function call */
 } SupportRequestIndexCondition;
 
+/*
+ * The SupportRequestRettype request type allows to calculate result type for
+ * functions that returns "any" type. It is designed for procedural specification
+ * return type.
+ */
+typedef struct SupportRequestRettype
+{
+	NodeTag		type;
+
+	/* Input fields */
+	List	   *funcname;
+	List	   *fargs;
+	Oid		   *actual_arg_types;
+	Oid		   *declared_arg_types;
+	int			nargs;
+
+	/* Output fields */
+	Oid			rettype;
+
+} SupportRequestRettype;
+
 #endif							/* SUPPORTNODES_H */
diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h
index 5a3b287eaf..7fc2ef1d37 100644
--- a/src/include/parser/parse_func.h
+++ b/src/include/parser/parse_func.h
@@ -41,7 +41,8 @@ extern FuncDetailCode func_get_detail(List *funcname,
 									  bool expand_variadic, bool expand_defaults,
 									  Oid *funcid, Oid *rettype,
 									  bool *retset, int *nvargs, Oid *vatype,
-									  Oid **true_typeids, List **argdefaults);
+									  Oid **true_typeids, List **argdefaults,
+									  Oid *support_func);
 
 extern int	func_match_argtypes(int nargs,
 								Oid *input_typeids,
parser-support-function-with-demo.patchtext/x-patch; charset=US-ASCII; name=parser-support-function-with-demo.patchDownload
diff --git a/contrib/decode/.gitignore b/contrib/decode/.gitignore
new file mode 100644
index 0000000000..5dcb3ff972
--- /dev/null
+++ b/contrib/decode/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/contrib/decode/Makefile b/contrib/decode/Makefile
new file mode 100644
index 0000000000..bb29732c61
--- /dev/null
+++ b/contrib/decode/Makefile
@@ -0,0 +1,20 @@
+# contrib/decode/Makefile
+
+MODULES = decode
+
+EXTENSION = decode
+DATA = decode--1.0.sql
+PGFILEDESC = "decode - example of parser support function"
+
+REGRESS = decode
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/decode
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/decode/decode--1.0.sql b/contrib/decode/decode--1.0.sql
new file mode 100644
index 0000000000..34408809ff
--- /dev/null
+++ b/contrib/decode/decode--1.0.sql
@@ -0,0 +1,26 @@
+/* contrib/decode/decode--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION decode" to load this file. \quit
+
+--
+--  PostgreSQL code for decode.
+--
+
+--
+-- Parser support function - allow to specify returning type when
+-- system with polymorphic variables is possible to use.
+--
+CREATE FUNCTION decode_support(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
+
+--
+-- decode function - example of function that returns "any" type
+--
+CREATE FUNCTION decode(variadic "any")
+RETURNS "any"
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE SUPPORT decode_support;
+
diff --git a/contrib/decode/decode.c b/contrib/decode/decode.c
new file mode 100644
index 0000000000..32919bf846
--- /dev/null
+++ b/contrib/decode/decode.c
@@ -0,0 +1,459 @@
+/*
+ * contrib/decode/decode.c
+ */
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "catalog/pg_type.h"
+#include "nodes/supportnodes.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_oper.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(decode_support);
+PG_FUNCTION_INFO_V1(decode);
+
+static void decode_detect_types(int nargs, Oid *argtypes, Oid *search_oid, Oid *result_oid);
+static Oid select_common_type_from_vector(int nargs, Oid *typeids, bool noerror);
+
+typedef struct decode_cache
+{
+	Oid			rettype;
+	int			nargs;
+	Oid		   *argtypes;
+	Oid		   *target_types;
+	Oid			search_typid;
+	Oid			result_typid;
+	CoercionPathType *ctype;
+	FmgrInfo   *cast_finfo;
+	FmgrInfo   *input_finfo;
+	Oid		   *typioparam;
+	FmgrInfo	eqop_finfo;
+} decode_cache;
+
+static void
+free_cache(decode_cache *cache)
+{
+	pfree(cache->argtypes);
+	pfree(cache->target_types);
+	pfree(cache->ctype);
+	pfree(cache->cast_finfo);
+	pfree(cache->input_finfo);
+	pfree(cache->typioparam);
+	pfree(cache);
+}
+
+/*
+ * prepare persistent cache used for fast internal parameter casting
+ */
+static decode_cache *
+build_cache(int nargs,
+			Oid *argtypes,
+			MemoryContext target_ctx)
+{
+	Oid		search_typid;
+	Oid		result_typid;
+	Oid			eqop;
+	decode_cache *cache;
+	MemoryContext		oldctx;
+	int		i;
+
+	oldctx = MemoryContextSwitchTo(target_ctx);
+
+	cache = palloc(sizeof(decode_cache));
+
+	cache->argtypes = palloc(nargs * sizeof(Oid));
+	cache->target_types = palloc(nargs * sizeof(Oid));
+	cache->ctype = palloc(nargs * sizeof(CoercionPathType));
+	cache->cast_finfo = palloc(nargs * sizeof(FmgrInfo));
+	cache->input_finfo = palloc(nargs * sizeof(FmgrInfo));
+	cache->typioparam = palloc(nargs * sizeof(Oid));
+
+	MemoryContextSwitchTo(oldctx);
+
+	decode_detect_types(nargs, argtypes, &search_typid, &result_typid);
+
+	cache->search_typid = search_typid;
+	cache->result_typid = result_typid;
+
+	for (i = 0; i < nargs; i++)
+	{
+		Oid		src_typid;
+		Oid		target_typid;
+
+		src_typid = cache->argtypes[i] = argtypes[i];
+
+		if (i == 0)
+			target_typid = search_typid;
+		else if (i % 2) /* even position */
+		{
+			if (i + 1 < nargs)
+				target_typid = search_typid;
+			else
+				/* last even argument is a default value */
+				target_typid = result_typid;
+		}
+		else /* odd position */
+			target_typid = result_typid;
+
+		cache->target_types[i] = target_typid;
+
+		/* prepare cast if it is necessary */
+		if (src_typid != target_typid)
+		{
+			Oid		funcid;
+
+			cache->ctype[i] = find_coercion_pathway(target_typid, src_typid,
+										COERCION_ASSIGNMENT, &funcid);
+			if (cache->ctype[i] == COERCION_PATH_NONE)
+				/* A previously detected cast is not available now */
+				elog(ERROR, "could not find cast from %u to %u",
+					 src_typid, target_typid);
+
+			if (cache->ctype[i] != COERCION_PATH_RELABELTYPE)
+			{
+				if (cache->ctype[i] == COERCION_PATH_FUNC)
+				{
+					fmgr_info(funcid, &cache->cast_finfo[i]);
+				}
+				else
+				{
+					Oid		outfuncoid;
+					Oid		infunc;
+					bool	typisvarlena;
+
+					getTypeOutputInfo(src_typid, &outfuncoid, &typisvarlena);
+					fmgr_info(outfuncoid, &cache->cast_finfo[i]);
+
+					getTypeInputInfo(target_typid, &infunc, &cache->typioparam[i]);
+					fmgr_info(infunc, &cache->input_finfo[i]);
+				}
+			}
+		}
+	}
+
+	get_sort_group_operators(search_typid, false, true, false, NULL, &eqop, NULL, NULL);
+	fmgr_info(get_opcode(eqop), &cache->eqop_finfo);
+
+	return cache;
+}
+
+/*
+ * Returns converted value into target type
+ */
+static Datum
+decode_cast(decode_cache *cache, int argn, Datum value)
+{
+	Datum result;
+
+	if (cache->argtypes[argn] != cache->target_types[argn])
+	{
+		if (cache->ctype[argn] == COERCION_PATH_RELABELTYPE)
+			result = value;
+		else if (cache->ctype[argn] == COERCION_PATH_FUNC)
+			result = FunctionCall1(&cache->cast_finfo[argn], value);
+		else
+		{
+			char	*str;
+
+			str = OutputFunctionCall(&cache->cast_finfo[argn], value);
+			result = InputFunctionCall(&cache->input_finfo[argn],
+									   str,
+									   cache->typioparam[argn],
+									   -1);
+		}
+	}
+	else
+		result = value;
+
+	return result;
+}
+
+/*
+ * Returns true, if cache can be used again
+ */
+static bool
+is_valid_cache(int nargs, Oid *argtypes, decode_cache *cache)
+{
+	if (cache)
+	{
+		if (nargs == cache->nargs)
+		{
+			int		i;
+
+			for (i = 0; i < nargs; i++)
+				if (argtypes[i] != cache->argtypes[i])
+					return false;
+
+			return true;
+		}
+	}
+
+	return false;
+}
+
+static void
+decode_detect_types(int nargs,
+					Oid *argtypes,
+					Oid *search_oid,	/* result */
+					Oid *result_oid)	/* result */
+{
+	Oid		search_typids[FUNC_MAX_ARGS];
+	Oid		result_typids[FUNC_MAX_ARGS];
+	int		search_nargs = 0;
+	int		result_nargs = 0;
+
+	Assert(nargs >= 3);
+
+	*search_oid = argtypes[1] != UNKNOWNOID ? argtypes[1] : TEXTOID;
+	*result_oid = argtypes[2] != UNKNOWNOID ? argtypes[2] : TEXTOID;
+
+	/* Search most common type if target type is not a text */
+	if (*search_oid != TEXTOID || *result_oid != TEXTOID)
+	{
+		int		i;
+
+		for (i = 0; i < nargs; i++)
+		{
+			if (i == 0)
+				search_typids[search_nargs++] = argtypes[0];
+			else if (i % 2) /* even position */
+			{
+				if (i + 1 < nargs)
+					search_typids[search_nargs++] = argtypes[i];
+				else
+					result_typids[result_nargs++] = argtypes[i];
+			}
+			else /* odd position */
+				result_typids[result_nargs++] = argtypes[i];
+		}
+
+		if (*search_oid != TEXTOID)
+		{
+			*search_oid = select_common_type_from_vector(search_nargs,
+														 search_typids,
+														 true);
+
+			if (!OidIsValid(*search_oid)) /* should not to be */
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("cannot to detect common type for search expression")));
+		}
+
+		if (*result_oid != TEXTOID)
+		{
+			*result_oid = select_common_type_from_vector(result_nargs,
+														 result_typids,
+														 true);
+
+			if (!OidIsValid(*result_oid)) /* should not to be */
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("cannot to detect common type for result expression")));
+		}
+	}
+}
+
+
+Datum
+decode_support(PG_FUNCTION_ARGS)
+{
+	Node	   *rawreq = (Node *) PG_GETARG_POINTER(0);
+	Node	   *ret = NULL;
+
+	if (IsA(rawreq, SupportRequestRettype))
+	{
+		SupportRequestRettype *req = (SupportRequestRettype *) rawreq;
+		Oid		search_oid;
+		Oid		result_oid;
+
+		if (req->nargs < 3)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("too few function arguments"),
+					 errhint("The decode function requires at least 3 arguments")));
+
+		decode_detect_types(req->nargs, req->actual_arg_types, &search_oid, &result_oid);
+
+		req->rettype = result_oid;
+
+		ret = (Node *) req;
+	}
+
+	PG_RETURN_POINTER(ret);
+}
+
+Datum
+decode(PG_FUNCTION_ARGS)
+{
+	Datum		expr = (Datum) 0;
+	bool	expr_isnull;
+	Oid		argtypes[FUNC_MAX_ARGS];
+	Oid		collation;
+	decode_cache *cache;
+	int		nargs = PG_NARGS();
+	int		result_argn;
+	int		i;
+
+	if (nargs < 3)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("too few function arguments"),
+				 errhint("The decode function requires at least 3 arguments")));
+
+	/* collect arg types */
+	for (i = 0; i < nargs; i++)
+		argtypes[i] = get_fn_expr_argtype(fcinfo->flinfo, i);
+
+	cache = (decode_cache *) fcinfo->flinfo->fn_extra;
+	if (!is_valid_cache(nargs, argtypes, cache))
+	{
+		if (cache)
+			free_cache(cache);
+		cache = build_cache(nargs, argtypes, fcinfo->flinfo->fn_mcxt);
+
+		fcinfo->flinfo->fn_extra = cache;
+	}
+
+	/* recheck rettype, should not be */
+	if (get_fn_expr_rettype(fcinfo->flinfo) != cache->result_typid)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("function has unexpected result type %d", get_fn_expr_rettype(fcinfo->flinfo)),
+				 errhint("The decode expects \"%s\" type",
+							format_type_be(cache->result_typid))));
+
+	/* try to set result_argn to position with default arrgument */
+	result_argn = nargs % 2 ? - 1 : nargs - 1;
+
+	expr_isnull = PG_ARGISNULL(0);
+	collation = PG_GET_COLLATION();
+
+	if (!expr_isnull)
+		expr = decode_cast(cache, 0, PG_GETARG_DATUM(0));
+
+	for (i = 1; i < nargs; i+= 2)
+	{
+		if (!expr_isnull && !PG_ARGISNULL(i) && i + 1 < nargs)
+		{
+			Datum		eqop_result;
+			Datum		value;
+
+			value = decode_cast(cache, i, PG_GETARG_DATUM(i));
+			eqop_result = FunctionCall2Coll(&cache->eqop_finfo, collation, expr, value);
+
+			if (DatumGetBool(eqop_result))
+			{
+				result_argn = i + 1;
+				break;
+			}
+		}
+		else if (expr_isnull && PG_ARGISNULL(i))
+		{
+			result_argn = i + 1;
+			break;
+		}
+	}
+
+	if (result_argn >= 0 && !PG_ARGISNULL(result_argn))
+		PG_RETURN_DATUM(decode_cast(cache, result_argn, PG_GETARG_DATUM(result_argn)));
+
+	PG_RETURN_NULL();
+}
+
+/*
+ * 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, lc
+	 * points to the first list item with type different from pexpr's; we need
+	 * not re-examine any items the previous loop advanced over.
+	 */
+	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;
+}
diff --git a/contrib/decode/decode.control b/contrib/decode/decode.control
new file mode 100644
index 0000000000..048499d4ab
--- /dev/null
+++ b/contrib/decode/decode.control
@@ -0,0 +1,5 @@
+# decode extension
+comment = 'decode - demo of function with parser support function'
+default_version = '1.0'
+module_pathname = '$libdir/decode'
+relocatable = true
diff --git a/contrib/decode/expected/decode.out b/contrib/decode/expected/decode.out
new file mode 100644
index 0000000000..dfb9edbdbc
--- /dev/null
+++ b/contrib/decode/expected/decode.out
@@ -0,0 +1,40 @@
+--
+--  Test decode function
+--
+CREATE EXTENSION decode;
+SELECT decode(1, 1, 5, 0);
+ decode 
+--------
+      5
+(1 row)
+
+SELECT decode(2, 1, 5, 0);
+ decode 
+--------
+      0
+(1 row)
+
+SELECT decode(1, 1, 5, 0.5);
+ decode 
+--------
+      5
+(1 row)
+
+SELECT decode(2, 1, 5, 0.5);
+ decode 
+--------
+    0.5
+(1 row)
+
+SELECT decode(1, 1, 'Ahoj', 'Nazdar');
+ decode 
+--------
+ Ahoj
+(1 row)
+
+SELECT decode(2, 1, 'Ahoj', 'Nazdar');
+ decode 
+--------
+ Nazdar
+(1 row)
+
diff --git a/contrib/decode/sql/decode.sql b/contrib/decode/sql/decode.sql
new file mode 100644
index 0000000000..ee93ad058c
--- /dev/null
+++ b/contrib/decode/sql/decode.sql
@@ -0,0 +1,13 @@
+--
+--  Test decode function
+--
+
+CREATE EXTENSION decode;
+
+SELECT decode(1, 1, 5, 0);
+SELECT decode(2, 1, 5, 0);
+SELECT decode(1, 1, 5, 0.5);
+SELECT decode(2, 1, 5, 0.5);
+
+SELECT decode(1, 1, 'Ahoj', 'Nazdar');
+SELECT decode(2, 1, 'Ahoj', 'Nazdar');
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 201242e796..640c506dd5 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -845,6 +845,7 @@ lookup_agg_function(List *fnName,
 	int			nvargs;
 	Oid			vatype;
 	Oid		   *true_oid_array;
+	Oid			support_func;
 	FuncDetailCode fdresult;
 	AclResult	aclresult;
 	int			i;
@@ -860,7 +861,8 @@ lookup_agg_function(List *fnName,
 							   nargs, input_types, false, false,
 							   &fnOid, rettype, &retset,
 							   &nvargs, &vatype,
-							   &true_oid_array, NULL);
+							   &true_oid_array, NULL,
+							   &support_func);
 
 	/* only valid case is a normal function not returning a set */
 	if (fdresult != FUNCDETAIL_NORMAL || !OidIsValid(fnOid))
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 8e926539e6..5974744668 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -22,6 +22,7 @@
 #include "lib/stringinfo.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "nodes/supportnodes.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
@@ -111,6 +112,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 	bool		retset;
 	int			nvargs;
 	Oid			vatype;
+	Oid			support_func;
 	FuncDetailCode fdresult;
 	char		aggkind = 0;
 	ParseCallbackState pcbstate;
@@ -265,7 +267,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 							   !func_variadic, true,
 							   &funcid, &rettype, &retset,
 							   &nvargs, &vatype,
-							   &declared_arg_types, &argdefaults);
+							   &declared_arg_types, &argdefaults,
+							   &support_func);
 
 	cancel_parser_errposition_callback(&pcbstate);
 
@@ -666,6 +669,31 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 	/* perform the necessary typecasting of arguments */
 	make_fn_arguments(pstate, fargs, actual_arg_types, declared_arg_types);
 
+	/*
+	 * When rettype is ANYOID we can call support function SupportRequestRettype if
+	 * it is available to get real type.
+	 */
+	if (rettype == ANYOID && OidIsValid(support_func))
+	{
+		SupportRequestRettype		req;
+		SupportRequestRettype	   *result;
+
+		req.type = T_SupportRequestRettype;
+		req.funcname = funcname;
+		req.fargs = fargs;
+		req.actual_arg_types = actual_arg_types;
+		req.declared_arg_types = declared_arg_types;
+		req.nargs = nargsplusdefs;
+
+		result = (SupportRequestRettype *)
+					DatumGetPointer(OidFunctionCall1(support_func,
+													 PointerGetDatum(&req)));
+
+		/* use result when it is valid */
+		if (result == &req)
+			rettype = result->rettype;
+	}
+
 	/*
 	 * If the function isn't actually variadic, forget any VARIADIC decoration
 	 * on the call.  (Perhaps we should throw an error instead, but
@@ -1392,7 +1420,8 @@ func_get_detail(List *funcname,
 				int *nvargs,	/* return value */
 				Oid *vatype,	/* return value */
 				Oid **true_typeids, /* return value */
-				List **argdefaults) /* optional return value */
+				List **argdefaults, /* optional return value */
+				Oid *support_func) /* return value */
 {
 	FuncCandidateList raw_candidates;
 	FuncCandidateList best_candidate;
@@ -1407,6 +1436,7 @@ func_get_detail(List *funcname,
 	*nvargs = 0;
 	*vatype = InvalidOid;
 	*true_typeids = NULL;
+	*support_func = InvalidOid;
 	if (argdefaults)
 		*argdefaults = NIL;
 
@@ -1519,6 +1549,7 @@ func_get_detail(List *funcname,
 					*nvargs = 0;
 					*vatype = InvalidOid;
 					*true_typeids = argtypes;
+					*support_func = InvalidOid;
 					return FUNCDETAIL_COERCION;
 				}
 			}
@@ -1616,6 +1647,7 @@ func_get_detail(List *funcname,
 		*rettype = pform->prorettype;
 		*retset = pform->proretset;
 		*vatype = pform->provariadic;
+		*support_func = pform->prosupport;
 		/* fetch default args if caller wants 'em */
 		if (argdefaults && best_candidate->ndargs > 0)
 		{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 13d5d542f9..a406f9c699 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10868,6 +10868,7 @@ generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
 	int			p_nvargs;
 	Oid			p_vatype;
 	Oid		   *p_true_typeids;
+	Oid			p_support_func;
 	bool		force_qualify = false;
 
 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
@@ -10922,7 +10923,8 @@ generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
 								   !use_variadic, true,
 								   &p_funcid, &p_rettype,
 								   &p_retset, &p_nvargs, &p_vatype,
-								   &p_true_typeids, NULL);
+								   &p_true_typeids, NULL,
+								   &p_support_func);
 	else
 	{
 		p_result = FUNCDETAIL_NOTFOUND;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 4e2fb39105..e2fb1cb83a 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -512,7 +512,8 @@ typedef enum NodeTag
 	T_SupportRequestSelectivity,	/* in nodes/supportnodes.h */
 	T_SupportRequestCost,		/* in nodes/supportnodes.h */
 	T_SupportRequestRows,		/* in nodes/supportnodes.h */
-	T_SupportRequestIndexCondition	/* in nodes/supportnodes.h */
+	T_SupportRequestIndexCondition,	/* in nodes/supportnodes.h */
+	T_SupportRequestRettype		/* in nodes/supportnodes.h */
 } NodeTag;
 
 /*
diff --git a/src/include/nodes/supportnodes.h b/src/include/nodes/supportnodes.h
index 460d75bd2d..abaf42cf96 100644
--- a/src/include/nodes/supportnodes.h
+++ b/src/include/nodes/supportnodes.h
@@ -239,4 +239,25 @@ typedef struct SupportRequestIndexCondition
 								 * equivalent of the function call */
 } SupportRequestIndexCondition;
 
+/*
+ * The SupportRequestRettype request type allows to calculate result type for
+ * functions that returns "any" type. It is designed for procedural specification
+ * return type.
+ */
+typedef struct SupportRequestRettype
+{
+	NodeTag		type;
+
+	/* Input fields */
+	List	   *funcname;
+	List	   *fargs;
+	Oid		   *actual_arg_types;
+	Oid		   *declared_arg_types;
+	int			nargs;
+
+	/* Output fields */
+	Oid			rettype;
+
+} SupportRequestRettype;
+
 #endif							/* SUPPORTNODES_H */
diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h
index 5a3b287eaf..7fc2ef1d37 100644
--- a/src/include/parser/parse_func.h
+++ b/src/include/parser/parse_func.h
@@ -41,7 +41,8 @@ extern FuncDetailCode func_get_detail(List *funcname,
 									  bool expand_variadic, bool expand_defaults,
 									  Oid *funcid, Oid *rettype,
 									  bool *retset, int *nvargs, Oid *vatype,
-									  Oid **true_typeids, List **argdefaults);
+									  Oid **true_typeids, List **argdefaults,
+									  Oid *support_func);
 
 extern int	func_match_argtypes(int nargs,
 								Oid *input_typeids,
#10Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#9)
1 attachment(s)
Re: proposal: type info support functions for functions that use "any" type

Hi

rebase

Pavel

Attachments:

parser-support-function-with-demo.patchtext/x-patch; charset=US-ASCII; name=parser-support-function-with-demo.patchDownload
diff --git a/contrib/decode/.gitignore b/contrib/decode/.gitignore
new file mode 100644
index 0000000000..5dcb3ff972
--- /dev/null
+++ b/contrib/decode/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/contrib/decode/Makefile b/contrib/decode/Makefile
new file mode 100644
index 0000000000..bb29732c61
--- /dev/null
+++ b/contrib/decode/Makefile
@@ -0,0 +1,20 @@
+# contrib/decode/Makefile
+
+MODULES = decode
+
+EXTENSION = decode
+DATA = decode--1.0.sql
+PGFILEDESC = "decode - example of parser support function"
+
+REGRESS = decode
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/decode
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/decode/decode--1.0.sql b/contrib/decode/decode--1.0.sql
new file mode 100644
index 0000000000..34408809ff
--- /dev/null
+++ b/contrib/decode/decode--1.0.sql
@@ -0,0 +1,26 @@
+/* contrib/decode/decode--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION decode" to load this file. \quit
+
+--
+--  PostgreSQL code for decode.
+--
+
+--
+-- Parser support function - allow to specify returning type when
+-- system with polymorphic variables is possible to use.
+--
+CREATE FUNCTION decode_support(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
+
+--
+-- decode function - example of function that returns "any" type
+--
+CREATE FUNCTION decode(variadic "any")
+RETURNS "any"
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE SUPPORT decode_support;
+
diff --git a/contrib/decode/decode.c b/contrib/decode/decode.c
new file mode 100644
index 0000000000..32919bf846
--- /dev/null
+++ b/contrib/decode/decode.c
@@ -0,0 +1,459 @@
+/*
+ * contrib/decode/decode.c
+ */
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "catalog/pg_type.h"
+#include "nodes/supportnodes.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_oper.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(decode_support);
+PG_FUNCTION_INFO_V1(decode);
+
+static void decode_detect_types(int nargs, Oid *argtypes, Oid *search_oid, Oid *result_oid);
+static Oid select_common_type_from_vector(int nargs, Oid *typeids, bool noerror);
+
+typedef struct decode_cache
+{
+	Oid			rettype;
+	int			nargs;
+	Oid		   *argtypes;
+	Oid		   *target_types;
+	Oid			search_typid;
+	Oid			result_typid;
+	CoercionPathType *ctype;
+	FmgrInfo   *cast_finfo;
+	FmgrInfo   *input_finfo;
+	Oid		   *typioparam;
+	FmgrInfo	eqop_finfo;
+} decode_cache;
+
+static void
+free_cache(decode_cache *cache)
+{
+	pfree(cache->argtypes);
+	pfree(cache->target_types);
+	pfree(cache->ctype);
+	pfree(cache->cast_finfo);
+	pfree(cache->input_finfo);
+	pfree(cache->typioparam);
+	pfree(cache);
+}
+
+/*
+ * prepare persistent cache used for fast internal parameter casting
+ */
+static decode_cache *
+build_cache(int nargs,
+			Oid *argtypes,
+			MemoryContext target_ctx)
+{
+	Oid		search_typid;
+	Oid		result_typid;
+	Oid			eqop;
+	decode_cache *cache;
+	MemoryContext		oldctx;
+	int		i;
+
+	oldctx = MemoryContextSwitchTo(target_ctx);
+
+	cache = palloc(sizeof(decode_cache));
+
+	cache->argtypes = palloc(nargs * sizeof(Oid));
+	cache->target_types = palloc(nargs * sizeof(Oid));
+	cache->ctype = palloc(nargs * sizeof(CoercionPathType));
+	cache->cast_finfo = palloc(nargs * sizeof(FmgrInfo));
+	cache->input_finfo = palloc(nargs * sizeof(FmgrInfo));
+	cache->typioparam = palloc(nargs * sizeof(Oid));
+
+	MemoryContextSwitchTo(oldctx);
+
+	decode_detect_types(nargs, argtypes, &search_typid, &result_typid);
+
+	cache->search_typid = search_typid;
+	cache->result_typid = result_typid;
+
+	for (i = 0; i < nargs; i++)
+	{
+		Oid		src_typid;
+		Oid		target_typid;
+
+		src_typid = cache->argtypes[i] = argtypes[i];
+
+		if (i == 0)
+			target_typid = search_typid;
+		else if (i % 2) /* even position */
+		{
+			if (i + 1 < nargs)
+				target_typid = search_typid;
+			else
+				/* last even argument is a default value */
+				target_typid = result_typid;
+		}
+		else /* odd position */
+			target_typid = result_typid;
+
+		cache->target_types[i] = target_typid;
+
+		/* prepare cast if it is necessary */
+		if (src_typid != target_typid)
+		{
+			Oid		funcid;
+
+			cache->ctype[i] = find_coercion_pathway(target_typid, src_typid,
+										COERCION_ASSIGNMENT, &funcid);
+			if (cache->ctype[i] == COERCION_PATH_NONE)
+				/* A previously detected cast is not available now */
+				elog(ERROR, "could not find cast from %u to %u",
+					 src_typid, target_typid);
+
+			if (cache->ctype[i] != COERCION_PATH_RELABELTYPE)
+			{
+				if (cache->ctype[i] == COERCION_PATH_FUNC)
+				{
+					fmgr_info(funcid, &cache->cast_finfo[i]);
+				}
+				else
+				{
+					Oid		outfuncoid;
+					Oid		infunc;
+					bool	typisvarlena;
+
+					getTypeOutputInfo(src_typid, &outfuncoid, &typisvarlena);
+					fmgr_info(outfuncoid, &cache->cast_finfo[i]);
+
+					getTypeInputInfo(target_typid, &infunc, &cache->typioparam[i]);
+					fmgr_info(infunc, &cache->input_finfo[i]);
+				}
+			}
+		}
+	}
+
+	get_sort_group_operators(search_typid, false, true, false, NULL, &eqop, NULL, NULL);
+	fmgr_info(get_opcode(eqop), &cache->eqop_finfo);
+
+	return cache;
+}
+
+/*
+ * Returns converted value into target type
+ */
+static Datum
+decode_cast(decode_cache *cache, int argn, Datum value)
+{
+	Datum result;
+
+	if (cache->argtypes[argn] != cache->target_types[argn])
+	{
+		if (cache->ctype[argn] == COERCION_PATH_RELABELTYPE)
+			result = value;
+		else if (cache->ctype[argn] == COERCION_PATH_FUNC)
+			result = FunctionCall1(&cache->cast_finfo[argn], value);
+		else
+		{
+			char	*str;
+
+			str = OutputFunctionCall(&cache->cast_finfo[argn], value);
+			result = InputFunctionCall(&cache->input_finfo[argn],
+									   str,
+									   cache->typioparam[argn],
+									   -1);
+		}
+	}
+	else
+		result = value;
+
+	return result;
+}
+
+/*
+ * Returns true, if cache can be used again
+ */
+static bool
+is_valid_cache(int nargs, Oid *argtypes, decode_cache *cache)
+{
+	if (cache)
+	{
+		if (nargs == cache->nargs)
+		{
+			int		i;
+
+			for (i = 0; i < nargs; i++)
+				if (argtypes[i] != cache->argtypes[i])
+					return false;
+
+			return true;
+		}
+	}
+
+	return false;
+}
+
+static void
+decode_detect_types(int nargs,
+					Oid *argtypes,
+					Oid *search_oid,	/* result */
+					Oid *result_oid)	/* result */
+{
+	Oid		search_typids[FUNC_MAX_ARGS];
+	Oid		result_typids[FUNC_MAX_ARGS];
+	int		search_nargs = 0;
+	int		result_nargs = 0;
+
+	Assert(nargs >= 3);
+
+	*search_oid = argtypes[1] != UNKNOWNOID ? argtypes[1] : TEXTOID;
+	*result_oid = argtypes[2] != UNKNOWNOID ? argtypes[2] : TEXTOID;
+
+	/* Search most common type if target type is not a text */
+	if (*search_oid != TEXTOID || *result_oid != TEXTOID)
+	{
+		int		i;
+
+		for (i = 0; i < nargs; i++)
+		{
+			if (i == 0)
+				search_typids[search_nargs++] = argtypes[0];
+			else if (i % 2) /* even position */
+			{
+				if (i + 1 < nargs)
+					search_typids[search_nargs++] = argtypes[i];
+				else
+					result_typids[result_nargs++] = argtypes[i];
+			}
+			else /* odd position */
+				result_typids[result_nargs++] = argtypes[i];
+		}
+
+		if (*search_oid != TEXTOID)
+		{
+			*search_oid = select_common_type_from_vector(search_nargs,
+														 search_typids,
+														 true);
+
+			if (!OidIsValid(*search_oid)) /* should not to be */
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("cannot to detect common type for search expression")));
+		}
+
+		if (*result_oid != TEXTOID)
+		{
+			*result_oid = select_common_type_from_vector(result_nargs,
+														 result_typids,
+														 true);
+
+			if (!OidIsValid(*result_oid)) /* should not to be */
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("cannot to detect common type for result expression")));
+		}
+	}
+}
+
+
+Datum
+decode_support(PG_FUNCTION_ARGS)
+{
+	Node	   *rawreq = (Node *) PG_GETARG_POINTER(0);
+	Node	   *ret = NULL;
+
+	if (IsA(rawreq, SupportRequestRettype))
+	{
+		SupportRequestRettype *req = (SupportRequestRettype *) rawreq;
+		Oid		search_oid;
+		Oid		result_oid;
+
+		if (req->nargs < 3)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("too few function arguments"),
+					 errhint("The decode function requires at least 3 arguments")));
+
+		decode_detect_types(req->nargs, req->actual_arg_types, &search_oid, &result_oid);
+
+		req->rettype = result_oid;
+
+		ret = (Node *) req;
+	}
+
+	PG_RETURN_POINTER(ret);
+}
+
+Datum
+decode(PG_FUNCTION_ARGS)
+{
+	Datum		expr = (Datum) 0;
+	bool	expr_isnull;
+	Oid		argtypes[FUNC_MAX_ARGS];
+	Oid		collation;
+	decode_cache *cache;
+	int		nargs = PG_NARGS();
+	int		result_argn;
+	int		i;
+
+	if (nargs < 3)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("too few function arguments"),
+				 errhint("The decode function requires at least 3 arguments")));
+
+	/* collect arg types */
+	for (i = 0; i < nargs; i++)
+		argtypes[i] = get_fn_expr_argtype(fcinfo->flinfo, i);
+
+	cache = (decode_cache *) fcinfo->flinfo->fn_extra;
+	if (!is_valid_cache(nargs, argtypes, cache))
+	{
+		if (cache)
+			free_cache(cache);
+		cache = build_cache(nargs, argtypes, fcinfo->flinfo->fn_mcxt);
+
+		fcinfo->flinfo->fn_extra = cache;
+	}
+
+	/* recheck rettype, should not be */
+	if (get_fn_expr_rettype(fcinfo->flinfo) != cache->result_typid)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("function has unexpected result type %d", get_fn_expr_rettype(fcinfo->flinfo)),
+				 errhint("The decode expects \"%s\" type",
+							format_type_be(cache->result_typid))));
+
+	/* try to set result_argn to position with default arrgument */
+	result_argn = nargs % 2 ? - 1 : nargs - 1;
+
+	expr_isnull = PG_ARGISNULL(0);
+	collation = PG_GET_COLLATION();
+
+	if (!expr_isnull)
+		expr = decode_cast(cache, 0, PG_GETARG_DATUM(0));
+
+	for (i = 1; i < nargs; i+= 2)
+	{
+		if (!expr_isnull && !PG_ARGISNULL(i) && i + 1 < nargs)
+		{
+			Datum		eqop_result;
+			Datum		value;
+
+			value = decode_cast(cache, i, PG_GETARG_DATUM(i));
+			eqop_result = FunctionCall2Coll(&cache->eqop_finfo, collation, expr, value);
+
+			if (DatumGetBool(eqop_result))
+			{
+				result_argn = i + 1;
+				break;
+			}
+		}
+		else if (expr_isnull && PG_ARGISNULL(i))
+		{
+			result_argn = i + 1;
+			break;
+		}
+	}
+
+	if (result_argn >= 0 && !PG_ARGISNULL(result_argn))
+		PG_RETURN_DATUM(decode_cast(cache, result_argn, PG_GETARG_DATUM(result_argn)));
+
+	PG_RETURN_NULL();
+}
+
+/*
+ * 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, lc
+	 * points to the first list item with type different from pexpr's; we need
+	 * not re-examine any items the previous loop advanced over.
+	 */
+	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;
+}
diff --git a/contrib/decode/decode.control b/contrib/decode/decode.control
new file mode 100644
index 0000000000..048499d4ab
--- /dev/null
+++ b/contrib/decode/decode.control
@@ -0,0 +1,5 @@
+# decode extension
+comment = 'decode - demo of function with parser support function'
+default_version = '1.0'
+module_pathname = '$libdir/decode'
+relocatable = true
diff --git a/contrib/decode/expected/decode.out b/contrib/decode/expected/decode.out
new file mode 100644
index 0000000000..dfb9edbdbc
--- /dev/null
+++ b/contrib/decode/expected/decode.out
@@ -0,0 +1,40 @@
+--
+--  Test decode function
+--
+CREATE EXTENSION decode;
+SELECT decode(1, 1, 5, 0);
+ decode 
+--------
+      5
+(1 row)
+
+SELECT decode(2, 1, 5, 0);
+ decode 
+--------
+      0
+(1 row)
+
+SELECT decode(1, 1, 5, 0.5);
+ decode 
+--------
+      5
+(1 row)
+
+SELECT decode(2, 1, 5, 0.5);
+ decode 
+--------
+    0.5
+(1 row)
+
+SELECT decode(1, 1, 'Ahoj', 'Nazdar');
+ decode 
+--------
+ Ahoj
+(1 row)
+
+SELECT decode(2, 1, 'Ahoj', 'Nazdar');
+ decode 
+--------
+ Nazdar
+(1 row)
+
diff --git a/contrib/decode/sql/decode.sql b/contrib/decode/sql/decode.sql
new file mode 100644
index 0000000000..ee93ad058c
--- /dev/null
+++ b/contrib/decode/sql/decode.sql
@@ -0,0 +1,13 @@
+--
+--  Test decode function
+--
+
+CREATE EXTENSION decode;
+
+SELECT decode(1, 1, 5, 0);
+SELECT decode(2, 1, 5, 0);
+SELECT decode(1, 1, 5, 0.5);
+SELECT decode(2, 1, 5, 0.5);
+
+SELECT decode(1, 1, 'Ahoj', 'Nazdar');
+SELECT decode(2, 1, 'Ahoj', 'Nazdar');
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 201242e796..640c506dd5 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -845,6 +845,7 @@ lookup_agg_function(List *fnName,
 	int			nvargs;
 	Oid			vatype;
 	Oid		   *true_oid_array;
+	Oid			support_func;
 	FuncDetailCode fdresult;
 	AclResult	aclresult;
 	int			i;
@@ -860,7 +861,8 @@ lookup_agg_function(List *fnName,
 							   nargs, input_types, false, false,
 							   &fnOid, rettype, &retset,
 							   &nvargs, &vatype,
-							   &true_oid_array, NULL);
+							   &true_oid_array, NULL,
+							   &support_func);
 
 	/* only valid case is a normal function not returning a set */
 	if (fdresult != FUNCDETAIL_NORMAL || !OidIsValid(fnOid))
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 8e926539e6..5974744668 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -22,6 +22,7 @@
 #include "lib/stringinfo.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "nodes/supportnodes.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
@@ -111,6 +112,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 	bool		retset;
 	int			nvargs;
 	Oid			vatype;
+	Oid			support_func;
 	FuncDetailCode fdresult;
 	char		aggkind = 0;
 	ParseCallbackState pcbstate;
@@ -265,7 +267,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 							   !func_variadic, true,
 							   &funcid, &rettype, &retset,
 							   &nvargs, &vatype,
-							   &declared_arg_types, &argdefaults);
+							   &declared_arg_types, &argdefaults,
+							   &support_func);
 
 	cancel_parser_errposition_callback(&pcbstate);
 
@@ -666,6 +669,31 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 	/* perform the necessary typecasting of arguments */
 	make_fn_arguments(pstate, fargs, actual_arg_types, declared_arg_types);
 
+	/*
+	 * When rettype is ANYOID we can call support function SupportRequestRettype if
+	 * it is available to get real type.
+	 */
+	if (rettype == ANYOID && OidIsValid(support_func))
+	{
+		SupportRequestRettype		req;
+		SupportRequestRettype	   *result;
+
+		req.type = T_SupportRequestRettype;
+		req.funcname = funcname;
+		req.fargs = fargs;
+		req.actual_arg_types = actual_arg_types;
+		req.declared_arg_types = declared_arg_types;
+		req.nargs = nargsplusdefs;
+
+		result = (SupportRequestRettype *)
+					DatumGetPointer(OidFunctionCall1(support_func,
+													 PointerGetDatum(&req)));
+
+		/* use result when it is valid */
+		if (result == &req)
+			rettype = result->rettype;
+	}
+
 	/*
 	 * If the function isn't actually variadic, forget any VARIADIC decoration
 	 * on the call.  (Perhaps we should throw an error instead, but
@@ -1392,7 +1420,8 @@ func_get_detail(List *funcname,
 				int *nvargs,	/* return value */
 				Oid *vatype,	/* return value */
 				Oid **true_typeids, /* return value */
-				List **argdefaults) /* optional return value */
+				List **argdefaults, /* optional return value */
+				Oid *support_func) /* return value */
 {
 	FuncCandidateList raw_candidates;
 	FuncCandidateList best_candidate;
@@ -1407,6 +1436,7 @@ func_get_detail(List *funcname,
 	*nvargs = 0;
 	*vatype = InvalidOid;
 	*true_typeids = NULL;
+	*support_func = InvalidOid;
 	if (argdefaults)
 		*argdefaults = NIL;
 
@@ -1519,6 +1549,7 @@ func_get_detail(List *funcname,
 					*nvargs = 0;
 					*vatype = InvalidOid;
 					*true_typeids = argtypes;
+					*support_func = InvalidOid;
 					return FUNCDETAIL_COERCION;
 				}
 			}
@@ -1616,6 +1647,7 @@ func_get_detail(List *funcname,
 		*rettype = pform->prorettype;
 		*retset = pform->proretset;
 		*vatype = pform->provariadic;
+		*support_func = pform->prosupport;
 		/* fetch default args if caller wants 'em */
 		if (argdefaults && best_candidate->ndargs > 0)
 		{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 3e64390d81..f09dadb8b3 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10868,6 +10868,7 @@ generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
 	int			p_nvargs;
 	Oid			p_vatype;
 	Oid		   *p_true_typeids;
+	Oid			p_support_func;
 	bool		force_qualify = false;
 
 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
@@ -10922,7 +10923,8 @@ generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
 								   !use_variadic, true,
 								   &p_funcid, &p_rettype,
 								   &p_retset, &p_nvargs, &p_vatype,
-								   &p_true_typeids, NULL);
+								   &p_true_typeids, NULL,
+								   &p_support_func);
 	else
 	{
 		p_result = FUNCDETAIL_NOTFOUND;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 3cbb08df92..6d781d174b 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -512,7 +512,8 @@ typedef enum NodeTag
 	T_SupportRequestSelectivity,	/* in nodes/supportnodes.h */
 	T_SupportRequestCost,		/* in nodes/supportnodes.h */
 	T_SupportRequestRows,		/* in nodes/supportnodes.h */
-	T_SupportRequestIndexCondition	/* in nodes/supportnodes.h */
+	T_SupportRequestIndexCondition,	/* in nodes/supportnodes.h */
+	T_SupportRequestRettype		/* in nodes/supportnodes.h */
 } NodeTag;
 
 /*
diff --git a/src/include/nodes/supportnodes.h b/src/include/nodes/supportnodes.h
index 460d75bd2d..abaf42cf96 100644
--- a/src/include/nodes/supportnodes.h
+++ b/src/include/nodes/supportnodes.h
@@ -239,4 +239,25 @@ typedef struct SupportRequestIndexCondition
 								 * equivalent of the function call */
 } SupportRequestIndexCondition;
 
+/*
+ * The SupportRequestRettype request type allows to calculate result type for
+ * functions that returns "any" type. It is designed for procedural specification
+ * return type.
+ */
+typedef struct SupportRequestRettype
+{
+	NodeTag		type;
+
+	/* Input fields */
+	List	   *funcname;
+	List	   *fargs;
+	Oid		   *actual_arg_types;
+	Oid		   *declared_arg_types;
+	int			nargs;
+
+	/* Output fields */
+	Oid			rettype;
+
+} SupportRequestRettype;
+
 #endif							/* SUPPORTNODES_H */
diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h
index 5a3b287eaf..7fc2ef1d37 100644
--- a/src/include/parser/parse_func.h
+++ b/src/include/parser/parse_func.h
@@ -41,7 +41,8 @@ extern FuncDetailCode func_get_detail(List *funcname,
 									  bool expand_variadic, bool expand_defaults,
 									  Oid *funcid, Oid *rettype,
 									  bool *retset, int *nvargs, Oid *vatype,
-									  Oid **true_typeids, List **argdefaults);
+									  Oid **true_typeids, List **argdefaults,
+									  Oid *support_func);
 
 extern int	func_match_argtypes(int nargs,
 								Oid *input_typeids,
#11Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#10)
1 attachment(s)
Re: proposal: type info support functions for functions that use "any" type

Hi

pá 16. 8. 2019 v 8:41 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:

Hi

rebase

another rebase

Regards

Pavel

Show quoted text

Pavel

Attachments:

parser-support-function-with-demo-20191128.patchtext/x-patch; charset=US-ASCII; name=parser-support-function-with-demo-20191128.patchDownload
diff --git a/contrib/Makefile b/contrib/Makefile
index 92184ed487..dafa42844d 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -15,6 +15,7 @@ SUBDIRS = \
 		citext		\
 		cube		\
 		dblink		\
+		decode		\
 		dict_int	\
 		dict_xsyn	\
 		earthdistance	\
diff --git a/contrib/decode/.gitignore b/contrib/decode/.gitignore
new file mode 100644
index 0000000000..5dcb3ff972
--- /dev/null
+++ b/contrib/decode/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/contrib/decode/Makefile b/contrib/decode/Makefile
new file mode 100644
index 0000000000..bb29732c61
--- /dev/null
+++ b/contrib/decode/Makefile
@@ -0,0 +1,20 @@
+# contrib/decode/Makefile
+
+MODULES = decode
+
+EXTENSION = decode
+DATA = decode--1.0.sql
+PGFILEDESC = "decode - example of parser support function"
+
+REGRESS = decode
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/decode
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/decode/decode--1.0.sql b/contrib/decode/decode--1.0.sql
new file mode 100644
index 0000000000..34408809ff
--- /dev/null
+++ b/contrib/decode/decode--1.0.sql
@@ -0,0 +1,26 @@
+/* contrib/decode/decode--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION decode" to load this file. \quit
+
+--
+--  PostgreSQL code for decode.
+--
+
+--
+-- Parser support function - allow to specify returning type when
+-- system with polymorphic variables is possible to use.
+--
+CREATE FUNCTION decode_support(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
+
+--
+-- decode function - example of function that returns "any" type
+--
+CREATE FUNCTION decode(variadic "any")
+RETURNS "any"
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE SUPPORT decode_support;
+
diff --git a/contrib/decode/decode.c b/contrib/decode/decode.c
new file mode 100644
index 0000000000..32919bf846
--- /dev/null
+++ b/contrib/decode/decode.c
@@ -0,0 +1,459 @@
+/*
+ * contrib/decode/decode.c
+ */
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "catalog/pg_type.h"
+#include "nodes/supportnodes.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_oper.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(decode_support);
+PG_FUNCTION_INFO_V1(decode);
+
+static void decode_detect_types(int nargs, Oid *argtypes, Oid *search_oid, Oid *result_oid);
+static Oid select_common_type_from_vector(int nargs, Oid *typeids, bool noerror);
+
+typedef struct decode_cache
+{
+	Oid			rettype;
+	int			nargs;
+	Oid		   *argtypes;
+	Oid		   *target_types;
+	Oid			search_typid;
+	Oid			result_typid;
+	CoercionPathType *ctype;
+	FmgrInfo   *cast_finfo;
+	FmgrInfo   *input_finfo;
+	Oid		   *typioparam;
+	FmgrInfo	eqop_finfo;
+} decode_cache;
+
+static void
+free_cache(decode_cache *cache)
+{
+	pfree(cache->argtypes);
+	pfree(cache->target_types);
+	pfree(cache->ctype);
+	pfree(cache->cast_finfo);
+	pfree(cache->input_finfo);
+	pfree(cache->typioparam);
+	pfree(cache);
+}
+
+/*
+ * prepare persistent cache used for fast internal parameter casting
+ */
+static decode_cache *
+build_cache(int nargs,
+			Oid *argtypes,
+			MemoryContext target_ctx)
+{
+	Oid		search_typid;
+	Oid		result_typid;
+	Oid			eqop;
+	decode_cache *cache;
+	MemoryContext		oldctx;
+	int		i;
+
+	oldctx = MemoryContextSwitchTo(target_ctx);
+
+	cache = palloc(sizeof(decode_cache));
+
+	cache->argtypes = palloc(nargs * sizeof(Oid));
+	cache->target_types = palloc(nargs * sizeof(Oid));
+	cache->ctype = palloc(nargs * sizeof(CoercionPathType));
+	cache->cast_finfo = palloc(nargs * sizeof(FmgrInfo));
+	cache->input_finfo = palloc(nargs * sizeof(FmgrInfo));
+	cache->typioparam = palloc(nargs * sizeof(Oid));
+
+	MemoryContextSwitchTo(oldctx);
+
+	decode_detect_types(nargs, argtypes, &search_typid, &result_typid);
+
+	cache->search_typid = search_typid;
+	cache->result_typid = result_typid;
+
+	for (i = 0; i < nargs; i++)
+	{
+		Oid		src_typid;
+		Oid		target_typid;
+
+		src_typid = cache->argtypes[i] = argtypes[i];
+
+		if (i == 0)
+			target_typid = search_typid;
+		else if (i % 2) /* even position */
+		{
+			if (i + 1 < nargs)
+				target_typid = search_typid;
+			else
+				/* last even argument is a default value */
+				target_typid = result_typid;
+		}
+		else /* odd position */
+			target_typid = result_typid;
+
+		cache->target_types[i] = target_typid;
+
+		/* prepare cast if it is necessary */
+		if (src_typid != target_typid)
+		{
+			Oid		funcid;
+
+			cache->ctype[i] = find_coercion_pathway(target_typid, src_typid,
+										COERCION_ASSIGNMENT, &funcid);
+			if (cache->ctype[i] == COERCION_PATH_NONE)
+				/* A previously detected cast is not available now */
+				elog(ERROR, "could not find cast from %u to %u",
+					 src_typid, target_typid);
+
+			if (cache->ctype[i] != COERCION_PATH_RELABELTYPE)
+			{
+				if (cache->ctype[i] == COERCION_PATH_FUNC)
+				{
+					fmgr_info(funcid, &cache->cast_finfo[i]);
+				}
+				else
+				{
+					Oid		outfuncoid;
+					Oid		infunc;
+					bool	typisvarlena;
+
+					getTypeOutputInfo(src_typid, &outfuncoid, &typisvarlena);
+					fmgr_info(outfuncoid, &cache->cast_finfo[i]);
+
+					getTypeInputInfo(target_typid, &infunc, &cache->typioparam[i]);
+					fmgr_info(infunc, &cache->input_finfo[i]);
+				}
+			}
+		}
+	}
+
+	get_sort_group_operators(search_typid, false, true, false, NULL, &eqop, NULL, NULL);
+	fmgr_info(get_opcode(eqop), &cache->eqop_finfo);
+
+	return cache;
+}
+
+/*
+ * Returns converted value into target type
+ */
+static Datum
+decode_cast(decode_cache *cache, int argn, Datum value)
+{
+	Datum result;
+
+	if (cache->argtypes[argn] != cache->target_types[argn])
+	{
+		if (cache->ctype[argn] == COERCION_PATH_RELABELTYPE)
+			result = value;
+		else if (cache->ctype[argn] == COERCION_PATH_FUNC)
+			result = FunctionCall1(&cache->cast_finfo[argn], value);
+		else
+		{
+			char	*str;
+
+			str = OutputFunctionCall(&cache->cast_finfo[argn], value);
+			result = InputFunctionCall(&cache->input_finfo[argn],
+									   str,
+									   cache->typioparam[argn],
+									   -1);
+		}
+	}
+	else
+		result = value;
+
+	return result;
+}
+
+/*
+ * Returns true, if cache can be used again
+ */
+static bool
+is_valid_cache(int nargs, Oid *argtypes, decode_cache *cache)
+{
+	if (cache)
+	{
+		if (nargs == cache->nargs)
+		{
+			int		i;
+
+			for (i = 0; i < nargs; i++)
+				if (argtypes[i] != cache->argtypes[i])
+					return false;
+
+			return true;
+		}
+	}
+
+	return false;
+}
+
+static void
+decode_detect_types(int nargs,
+					Oid *argtypes,
+					Oid *search_oid,	/* result */
+					Oid *result_oid)	/* result */
+{
+	Oid		search_typids[FUNC_MAX_ARGS];
+	Oid		result_typids[FUNC_MAX_ARGS];
+	int		search_nargs = 0;
+	int		result_nargs = 0;
+
+	Assert(nargs >= 3);
+
+	*search_oid = argtypes[1] != UNKNOWNOID ? argtypes[1] : TEXTOID;
+	*result_oid = argtypes[2] != UNKNOWNOID ? argtypes[2] : TEXTOID;
+
+	/* Search most common type if target type is not a text */
+	if (*search_oid != TEXTOID || *result_oid != TEXTOID)
+	{
+		int		i;
+
+		for (i = 0; i < nargs; i++)
+		{
+			if (i == 0)
+				search_typids[search_nargs++] = argtypes[0];
+			else if (i % 2) /* even position */
+			{
+				if (i + 1 < nargs)
+					search_typids[search_nargs++] = argtypes[i];
+				else
+					result_typids[result_nargs++] = argtypes[i];
+			}
+			else /* odd position */
+				result_typids[result_nargs++] = argtypes[i];
+		}
+
+		if (*search_oid != TEXTOID)
+		{
+			*search_oid = select_common_type_from_vector(search_nargs,
+														 search_typids,
+														 true);
+
+			if (!OidIsValid(*search_oid)) /* should not to be */
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("cannot to detect common type for search expression")));
+		}
+
+		if (*result_oid != TEXTOID)
+		{
+			*result_oid = select_common_type_from_vector(result_nargs,
+														 result_typids,
+														 true);
+
+			if (!OidIsValid(*result_oid)) /* should not to be */
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("cannot to detect common type for result expression")));
+		}
+	}
+}
+
+
+Datum
+decode_support(PG_FUNCTION_ARGS)
+{
+	Node	   *rawreq = (Node *) PG_GETARG_POINTER(0);
+	Node	   *ret = NULL;
+
+	if (IsA(rawreq, SupportRequestRettype))
+	{
+		SupportRequestRettype *req = (SupportRequestRettype *) rawreq;
+		Oid		search_oid;
+		Oid		result_oid;
+
+		if (req->nargs < 3)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("too few function arguments"),
+					 errhint("The decode function requires at least 3 arguments")));
+
+		decode_detect_types(req->nargs, req->actual_arg_types, &search_oid, &result_oid);
+
+		req->rettype = result_oid;
+
+		ret = (Node *) req;
+	}
+
+	PG_RETURN_POINTER(ret);
+}
+
+Datum
+decode(PG_FUNCTION_ARGS)
+{
+	Datum		expr = (Datum) 0;
+	bool	expr_isnull;
+	Oid		argtypes[FUNC_MAX_ARGS];
+	Oid		collation;
+	decode_cache *cache;
+	int		nargs = PG_NARGS();
+	int		result_argn;
+	int		i;
+
+	if (nargs < 3)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("too few function arguments"),
+				 errhint("The decode function requires at least 3 arguments")));
+
+	/* collect arg types */
+	for (i = 0; i < nargs; i++)
+		argtypes[i] = get_fn_expr_argtype(fcinfo->flinfo, i);
+
+	cache = (decode_cache *) fcinfo->flinfo->fn_extra;
+	if (!is_valid_cache(nargs, argtypes, cache))
+	{
+		if (cache)
+			free_cache(cache);
+		cache = build_cache(nargs, argtypes, fcinfo->flinfo->fn_mcxt);
+
+		fcinfo->flinfo->fn_extra = cache;
+	}
+
+	/* recheck rettype, should not be */
+	if (get_fn_expr_rettype(fcinfo->flinfo) != cache->result_typid)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("function has unexpected result type %d", get_fn_expr_rettype(fcinfo->flinfo)),
+				 errhint("The decode expects \"%s\" type",
+							format_type_be(cache->result_typid))));
+
+	/* try to set result_argn to position with default arrgument */
+	result_argn = nargs % 2 ? - 1 : nargs - 1;
+
+	expr_isnull = PG_ARGISNULL(0);
+	collation = PG_GET_COLLATION();
+
+	if (!expr_isnull)
+		expr = decode_cast(cache, 0, PG_GETARG_DATUM(0));
+
+	for (i = 1; i < nargs; i+= 2)
+	{
+		if (!expr_isnull && !PG_ARGISNULL(i) && i + 1 < nargs)
+		{
+			Datum		eqop_result;
+			Datum		value;
+
+			value = decode_cast(cache, i, PG_GETARG_DATUM(i));
+			eqop_result = FunctionCall2Coll(&cache->eqop_finfo, collation, expr, value);
+
+			if (DatumGetBool(eqop_result))
+			{
+				result_argn = i + 1;
+				break;
+			}
+		}
+		else if (expr_isnull && PG_ARGISNULL(i))
+		{
+			result_argn = i + 1;
+			break;
+		}
+	}
+
+	if (result_argn >= 0 && !PG_ARGISNULL(result_argn))
+		PG_RETURN_DATUM(decode_cast(cache, result_argn, PG_GETARG_DATUM(result_argn)));
+
+	PG_RETURN_NULL();
+}
+
+/*
+ * 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, lc
+	 * points to the first list item with type different from pexpr's; we need
+	 * not re-examine any items the previous loop advanced over.
+	 */
+	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;
+}
diff --git a/contrib/decode/decode.control b/contrib/decode/decode.control
new file mode 100644
index 0000000000..048499d4ab
--- /dev/null
+++ b/contrib/decode/decode.control
@@ -0,0 +1,5 @@
+# decode extension
+comment = 'decode - demo of function with parser support function'
+default_version = '1.0'
+module_pathname = '$libdir/decode'
+relocatable = true
diff --git a/contrib/decode/expected/decode.out b/contrib/decode/expected/decode.out
new file mode 100644
index 0000000000..dfb9edbdbc
--- /dev/null
+++ b/contrib/decode/expected/decode.out
@@ -0,0 +1,40 @@
+--
+--  Test decode function
+--
+CREATE EXTENSION decode;
+SELECT decode(1, 1, 5, 0);
+ decode 
+--------
+      5
+(1 row)
+
+SELECT decode(2, 1, 5, 0);
+ decode 
+--------
+      0
+(1 row)
+
+SELECT decode(1, 1, 5, 0.5);
+ decode 
+--------
+      5
+(1 row)
+
+SELECT decode(2, 1, 5, 0.5);
+ decode 
+--------
+    0.5
+(1 row)
+
+SELECT decode(1, 1, 'Ahoj', 'Nazdar');
+ decode 
+--------
+ Ahoj
+(1 row)
+
+SELECT decode(2, 1, 'Ahoj', 'Nazdar');
+ decode 
+--------
+ Nazdar
+(1 row)
+
diff --git a/contrib/decode/sql/decode.sql b/contrib/decode/sql/decode.sql
new file mode 100644
index 0000000000..ee93ad058c
--- /dev/null
+++ b/contrib/decode/sql/decode.sql
@@ -0,0 +1,13 @@
+--
+--  Test decode function
+--
+
+CREATE EXTENSION decode;
+
+SELECT decode(1, 1, 5, 0);
+SELECT decode(2, 1, 5, 0);
+SELECT decode(1, 1, 5, 0.5);
+SELECT decode(2, 1, 5, 0.5);
+
+SELECT decode(1, 1, 'Ahoj', 'Nazdar');
+SELECT decode(2, 1, 'Ahoj', 'Nazdar');
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 1ac235a0f4..fe0f440353 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -845,6 +845,7 @@ lookup_agg_function(List *fnName,
 	int			nvargs;
 	Oid			vatype;
 	Oid		   *true_oid_array;
+	Oid			support_func;
 	FuncDetailCode fdresult;
 	AclResult	aclresult;
 	int			i;
@@ -860,7 +861,8 @@ lookup_agg_function(List *fnName,
 							   nargs, input_types, false, false,
 							   &fnOid, rettype, &retset,
 							   &nvargs, &vatype,
-							   &true_oid_array, NULL);
+							   &true_oid_array, NULL,
+							   &support_func);
 
 	/* only valid case is a normal function not returning a set */
 	if (fdresult != FUNCDETAIL_NORMAL || !OidIsValid(fnOid))
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index d9c6dc1901..978ceb1290 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -22,6 +22,7 @@
 #include "lib/stringinfo.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "nodes/supportnodes.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
@@ -111,6 +112,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 	bool		retset;
 	int			nvargs;
 	Oid			vatype;
+	Oid			support_func;
 	FuncDetailCode fdresult;
 	char		aggkind = 0;
 	ParseCallbackState pcbstate;
@@ -265,7 +267,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 							   !func_variadic, true,
 							   &funcid, &rettype, &retset,
 							   &nvargs, &vatype,
-							   &declared_arg_types, &argdefaults);
+							   &declared_arg_types, &argdefaults,
+							   &support_func);
 
 	cancel_parser_errposition_callback(&pcbstate);
 
@@ -666,6 +669,31 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 	/* perform the necessary typecasting of arguments */
 	make_fn_arguments(pstate, fargs, actual_arg_types, declared_arg_types);
 
+	/*
+	 * When rettype is ANYOID we can call support function SupportRequestRettype if
+	 * it is available to get real type.
+	 */
+	if (rettype == ANYOID && OidIsValid(support_func))
+	{
+		SupportRequestRettype		req;
+		SupportRequestRettype	   *result;
+
+		req.type = T_SupportRequestRettype;
+		req.funcname = funcname;
+		req.fargs = fargs;
+		req.actual_arg_types = actual_arg_types;
+		req.declared_arg_types = declared_arg_types;
+		req.nargs = nargsplusdefs;
+
+		result = (SupportRequestRettype *)
+					DatumGetPointer(OidFunctionCall1(support_func,
+													 PointerGetDatum(&req)));
+
+		/* use result when it is valid */
+		if (result == &req)
+			rettype = result->rettype;
+	}
+
 	/*
 	 * If the function isn't actually variadic, forget any VARIADIC decoration
 	 * on the call.  (Perhaps we should throw an error instead, but
@@ -1392,7 +1420,8 @@ func_get_detail(List *funcname,
 				int *nvargs,	/* return value */
 				Oid *vatype,	/* return value */
 				Oid **true_typeids, /* return value */
-				List **argdefaults) /* optional return value */
+				List **argdefaults, /* optional return value */
+				Oid *support_func) /* return value */
 {
 	FuncCandidateList raw_candidates;
 	FuncCandidateList best_candidate;
@@ -1404,6 +1433,7 @@ func_get_detail(List *funcname,
 	*nvargs = 0;
 	*vatype = InvalidOid;
 	*true_typeids = NULL;
+	*support_func = InvalidOid;
 	if (argdefaults)
 		*argdefaults = NIL;
 
@@ -1518,6 +1548,7 @@ func_get_detail(List *funcname,
 					*nvargs = 0;
 					*vatype = InvalidOid;
 					*true_typeids = argtypes;
+					*support_func = InvalidOid;
 					return FUNCDETAIL_COERCION;
 				}
 			}
@@ -1615,6 +1646,7 @@ func_get_detail(List *funcname,
 		*rettype = pform->prorettype;
 		*retset = pform->proretset;
 		*vatype = pform->provariadic;
+		*support_func = pform->prosupport;
 		/* fetch default args if caller wants 'em */
 		if (argdefaults && best_candidate->ndargs > 0)
 		{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 13685a0a0e..c4347b037b 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10880,6 +10880,7 @@ generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
 	int			p_nvargs;
 	Oid			p_vatype;
 	Oid		   *p_true_typeids;
+	Oid			p_support_func;
 	bool		force_qualify = false;
 
 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
@@ -10934,7 +10935,8 @@ generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
 								   !use_variadic, true,
 								   &p_funcid, &p_rettype,
 								   &p_retset, &p_nvargs, &p_vatype,
-								   &p_true_typeids, NULL);
+								   &p_true_typeids, NULL,
+								   &p_support_func);
 	else
 	{
 		p_result = FUNCDETAIL_NOTFOUND;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index bce2d59b0d..0e636d95e6 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -513,7 +513,8 @@ typedef enum NodeTag
 	T_SupportRequestSelectivity,	/* in nodes/supportnodes.h */
 	T_SupportRequestCost,		/* in nodes/supportnodes.h */
 	T_SupportRequestRows,		/* in nodes/supportnodes.h */
-	T_SupportRequestIndexCondition	/* in nodes/supportnodes.h */
+	T_SupportRequestIndexCondition,	/* in nodes/supportnodes.h */
+	T_SupportRequestRettype		/* in nodes/supportnodes.h */
 } NodeTag;
 
 /*
diff --git a/src/include/nodes/supportnodes.h b/src/include/nodes/supportnodes.h
index 460d75bd2d..abaf42cf96 100644
--- a/src/include/nodes/supportnodes.h
+++ b/src/include/nodes/supportnodes.h
@@ -239,4 +239,25 @@ typedef struct SupportRequestIndexCondition
 								 * equivalent of the function call */
 } SupportRequestIndexCondition;
 
+/*
+ * The SupportRequestRettype request type allows to calculate result type for
+ * functions that returns "any" type. It is designed for procedural specification
+ * return type.
+ */
+typedef struct SupportRequestRettype
+{
+	NodeTag		type;
+
+	/* Input fields */
+	List	   *funcname;
+	List	   *fargs;
+	Oid		   *actual_arg_types;
+	Oid		   *declared_arg_types;
+	int			nargs;
+
+	/* Output fields */
+	Oid			rettype;
+
+} SupportRequestRettype;
+
 #endif							/* SUPPORTNODES_H */
diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h
index 5a3b287eaf..7fc2ef1d37 100644
--- a/src/include/parser/parse_func.h
+++ b/src/include/parser/parse_func.h
@@ -41,7 +41,8 @@ extern FuncDetailCode func_get_detail(List *funcname,
 									  bool expand_variadic, bool expand_defaults,
 									  Oid *funcid, Oid *rettype,
 									  bool *retset, int *nvargs, Oid *vatype,
-									  Oid **true_typeids, List **argdefaults);
+									  Oid **true_typeids, List **argdefaults,
+									  Oid *support_func);
 
 extern int	func_match_argtypes(int nargs,
 								Oid *input_typeids,
#12Tom Lane
tgl@sss.pgh.pa.us
In reply to: Pavel Stehule (#11)
Re: proposal: type info support functions for functions that use "any" type

Pavel Stehule <pavel.stehule@gmail.com> writes:

[ parser-support-function-with-demo-20191128.patch ]

TBH, I'm still not convinced that this is a good idea. Restricting
the support function to only change the function's return type is
safer than the original proposal, but it's still not terribly safe.
If you change the support function's algorithm in any way, how do
you know whether you've broken existing stored queries? If the
support function consults external resources to make its choice
(perhaps checking the existence of a cast), where could we record
that the query depends on the existence of that cast? There'd be
no visible trace of that in the query parsetree.

I'm also still not convinced that this idea allows doing anything
that can't be done just as well with polymorphism. It would be a
really bad idea for the support function to be examining the values
of the arguments (else what happens when they're not constants?).
So all you can do is look at their types, and then it seems like
the things you can usefully do are pretty much like polymorphism,
i.e. select some one of the input types, or a related type such
as an array type or element type. If there are gaps in what you
can express with polymorphism, I'd much rather spend effort on
improving that facility than in adding something that is only
accessible to advanced C coders. (Yes, I know I've been slacking
on reviewing [1]https://commitfest.postgresql.org/26/1911/.)

Lastly, I still think that this patch doesn't begin to address
all the places that would have to know about the feature. There's
a lot of places that know about polymorphism --- if this is
polymorphism on steroids, which it is, then why don't all of those
places need to be touched?

On the whole I think we should reject this idea.

regards, tom lane

[1]: https://commitfest.postgresql.org/26/1911/

#13Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#12)
Re: proposal: type info support functions for functions that use "any" type

út 14. 1. 2020 v 22:09 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:

Pavel Stehule <pavel.stehule@gmail.com> writes:

[ parser-support-function-with-demo-20191128.patch ]

TBH, I'm still not convinced that this is a good idea. Restricting
the support function to only change the function's return type is
safer than the original proposal, but it's still not terribly safe.
If you change the support function's algorithm in any way, how do
you know whether you've broken existing stored queries? If the
support function consults external resources to make its choice
(perhaps checking the existence of a cast), where could we record
that the query depends on the existence of that cast? There'd be
no visible trace of that in the query parsetree.

This risk is real and cannot be simply solved without more complications.

Can be solution to limit and enforce this functionality only for extensions
that be initialized from shared_preload_libraries or
local_preload_libraries?

I'm also still not convinced that this idea allows doing anything
that can't be done just as well with polymorphism. It would be a
really bad idea for the support function to be examining the values
of the arguments (else what happens when they're not constants?).
So all you can do is look at their types, and then it seems like
the things you can usefully do are pretty much like polymorphism,
i.e. select some one of the input types, or a related type such
as an array type or element type. If there are gaps in what you
can express with polymorphism, I'd much rather spend effort on
improving that facility than in adding something that is only
accessible to advanced C coders. (Yes, I know I've been slacking
on reviewing [1].)

For my purpose critical information is type. I don't need to work with
constant, but I can imagine, so some API can be nice to work with constant
value.
Yes, I can solve lot of things by patch [1], but not all, and this patch
shorter, and almost trivial.

Lastly, I still think that this patch doesn't begin to address
all the places that would have to know about the feature. There's
a lot of places that know about polymorphism --- if this is
polymorphism on steroids, which it is, then why don't all of those
places need to be touched?

I am sorry, I don't understand last sentence?

Show quoted text

On the whole I think we should reject this idea.

regards, tom lane

[1] https://commitfest.postgresql.org/26/1911/

#14Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#13)
Re: proposal: type info support functions for functions that use "any" type

st 15. 1. 2020 v 11:04 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:

út 14. 1. 2020 v 22:09 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:

Pavel Stehule <pavel.stehule@gmail.com> writes:

[ parser-support-function-with-demo-20191128.patch ]

TBH, I'm still not convinced that this is a good idea. Restricting
the support function to only change the function's return type is
safer than the original proposal, but it's still not terribly safe.
If you change the support function's algorithm in any way, how do
you know whether you've broken existing stored queries? If the
support function consults external resources to make its choice
(perhaps checking the existence of a cast), where could we record
that the query depends on the existence of that cast? There'd be
no visible trace of that in the query parsetree.

This risk is real and cannot be simply solved without more complications.

Can be solution to limit and enforce this functionality only for
extensions that be initialized from shared_preload_libraries or
local_preload_libraries?

When we check, so used function is started from dynamic loaded extension,
we can raise a error. It's not too great for upgrades, but I expect upgrade
of this kind extension is very similar like Postgres - and the restart can
be together.

I'm also still not convinced that this idea allows doing anything
that can't be done just as well with polymorphism. It would be a
really bad idea for the support function to be examining the values
of the arguments (else what happens when they're not constants?).
So all you can do is look at their types, and then it seems like
the things you can usefully do are pretty much like polymorphism,
i.e. select some one of the input types, or a related type such
as an array type or element type. If there are gaps in what you
can express with polymorphism, I'd much rather spend effort on
improving that facility than in adding something that is only
accessible to advanced C coders. (Yes, I know I've been slacking
on reviewing [1].)

For my purpose critical information is type. I don't need to work with
constant, but I can imagine, so some API can be nice to work with constant
value.
Yes, I can solve lot of things by patch [1], but not all, and this patch
shorter, and almost trivial.

All this discussion is motivated by my work on Orafce extension -
https://github.com/orafce/orafce

Unfortunately implementation of "decode" functions is not possible with
patch [1]. Now I have 55 instances of "decode" function and I am sure, I
don't cover all.

With this patch (polymorphism on stereoids :)), I can do it very simple,
and quickly. This functions and other similar.

The patch was very simple, so I think, maybe wrongly, so it is acceptable
way.

Our polymorphism is strong, and if I design code natively for Postgres,
than it is perfect. But It doesn't allow to implement some simple functions
that are used in other databases. With this small patch I can cover almost
all situations - and very simply.

I don't want to increase complexity of polymorphism rules more - [1] is
maximum, what we can implement with acceptable costs, but this generic
system is sometimes not enough.

But I invite any design, how this problem can be solved.

Any ideas?

Show quoted text

Lastly, I still think that this patch doesn't begin to address
all the places that would have to know about the feature. There's
a lot of places that know about polymorphism --- if this is
polymorphism on steroids, which it is, then why don't all of those
places need to be touched?

I am sorry, I don't understand last sentence?

On the whole I think we should reject this idea.

regards, tom lane

[1] https://commitfest.postgresql.org/26/1911/

#15Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#12)
Re: proposal: type info support functions for functions that use "any" type

Hi

út 14. 1. 2020 v 22:09 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:

Pavel Stehule <pavel.stehule@gmail.com> writes:

[ parser-support-function-with-demo-20191128.patch ]

TBH, I'm still not convinced that this is a good idea. Restricting
the support function to only change the function's return type is
safer than the original proposal, but it's still not terribly safe.
If you change the support function's algorithm in any way, how do
you know whether you've broken existing stored queries? If the
support function consults external resources to make its choice
(perhaps checking the existence of a cast), where could we record
that the query depends on the existence of that cast? There'd be
no visible trace of that in the query parsetree.

I reread all related mails and I think so it should be safe - or there is
same risk like using any C extensions for functions or hooks.

I use a example from demo

+CREATE FUNCTION decode_support(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
+
+--
+-- decode function - example of function that returns "any" type
+--
+CREATE FUNCTION decode(variadic "any")
+RETURNS "any"
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE SUPPORT decode_support;

The support function (and implementation) is joined with "decode" function.
So I cannot to change the behave of support function without reloading a
extension, and it needs typically session reconnect.

I'm also still not convinced that this idea allows doing anything
that can't be done just as well with polymorphism. It would be a
really bad idea for the support function to be examining the values
of the arguments (else what happens when they're not constants?).
So all you can do is look at their types, and then it seems like
the things you can usefully do are pretty much like polymorphism,
i.e. select some one of the input types, or a related type such
as an array type or element type. If there are gaps in what you
can express with polymorphism, I'd much rather spend effort on
improving that facility than in adding something that is only
accessible to advanced C coders. (Yes, I know I've been slacking
on reviewing [1].)

The design is based not on values, just on types. I don't need to know a
value, I need to know a type.

Currently our polymorphism is not enough - and is necessary to use "any"
datatype. This patch just add a possibility to use "any" as return type.

I spent on this topic lot of time and one result is patch [1]. This patch
increase situation lot of, but cannot to cover all. There are strong limits
for variadic usage.

This patch is really not about values, it is about types - and about more
possibility (and more elasticity) to control result type.

Lastly, I still think that this patch doesn't begin to address
all the places that would have to know about the feature. There's
a lot of places that know about polymorphism --- if this is
polymorphism on steroids, which it is, then why don't all of those
places need to be touched?

It is working with "any" type, and then it can be very small, because the
all work with this type is moved to extension.

On the whole I think we should reject this idea.

I will accept any your opinion. Please, try to understand to me as Orafce
developer, maintainer. I would to clean this extension, and current state
of polymorphism (with patch [1]) doesn't allow it.

I am open to any proposals, ideas.

Regards

Pavel

Show quoted text

regards, tom lane

[1] https://commitfest.postgresql.org/26/1911/

#16Daniel Gustafsson
daniel@yesql.se
In reply to: Pavel Stehule (#15)
Re: proposal: type info support functions for functions that use "any" type

On 26 Jan 2020, at 16:33, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I reread all related mails and I think so it should be safe - or there is same risk like using any C extensions for functions or hooks.

This patch has been bumped in CFs for the past year, with the thread stalled
and the last review comment being in support of rejection. Tom, do you still
feel it should be rejected in light of Pavel's latest posts?

cheers ./daniel

#17Tom Lane
tgl@sss.pgh.pa.us
In reply to: Daniel Gustafsson (#16)
Re: proposal: type info support functions for functions that use "any" type

Daniel Gustafsson <daniel@yesql.se> writes:

This patch has been bumped in CFs for the past year, with the thread stalled
and the last review comment being in support of rejection. Tom, do you still
feel it should be rejected in light of Pavel's latest posts?

I have seen no convincing response to the concerns I raised in my
last message in the thread [1]/messages/by-id/31501.1579036195@sss.pgh.pa.us, to wit that

1. I think the "flexibility" of letting a support function resolve the
output type in some unspecified way is mostly illusory, because if it
doesn't do it in a way that's morally equivalent to polymorphism, it's
doing it wrong. Also, I'm not that excited about improving polymorphism
in a way that is only accessible with specialized C code. The example of
Oracle-like DECODE() could be handled about as well if we had a second set
of anycompatible-style polymorphic types, that is something like

decode(expr anycompatible,
search1 anycompatible, result1 anycompatible2,
search2 anycompatible, result2 anycompatible2,
search3 anycompatible, result3 anycompatible2,
...
) returns anycompatible2;

Admittedly, you'd need to write a separate declaration for each number of
arguments you wanted to support, but they could all point at the same C
function --- which'd be a lot simpler than in this patch, since it would
not need to deal with any type coercions, only comparisons.

I also argue that to the extent that the support function is reinventing
polymorphism internally, it's going to be inferior to the parser's
version. As an example, with Pavel's sample implementation, if a
particular query needs a coercion from type X to type Y, that's nowhere
visible in the parse tree. So you could drop the cast without being told
that view so-and-so depends on it, leading to a run-time failure next time
you try to use that view. Doing the same thing with normal polymorphism,
the X-to-Y cast function would be used in the parse tree and so we'd know
about the dependency.

2. I have no faith that the proposed implementation is correct or
complete. As I complained earlier, a lot of places have special-case
handling for polymorphism, and it seems like every one of them would
need to know about this feature too. That is, to the extent that
this patch's footprint is smaller than commit 24e2885ee -- which it
is, by a lot -- I think those are bugs of omission. It will not work
to have a situation where some parts of the backend resolve a function's
result type as one thing and others resolve it as something else thanks to
failure to account for this new feature. As a concrete example, it looks
like we'd fail pretty hard if someone tried to use this facility in an
aggregate support function.

So my opinion is still what it was in January.

regards, tom lane

[1]: /messages/by-id/31501.1579036195@sss.pgh.pa.us

#18Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#17)
Re: proposal: type info support functions for functions that use "any" type

pá 31. 7. 2020 v 2:32 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:

Daniel Gustafsson <daniel@yesql.se> writes:

This patch has been bumped in CFs for the past year, with the thread

stalled

and the last review comment being in support of rejection. Tom, do you

still

feel it should be rejected in light of Pavel's latest posts?

I have seen no convincing response to the concerns I raised in my
last message in the thread [1], to wit that

1. I think the "flexibility" of letting a support function resolve the
output type in some unspecified way is mostly illusory, because if it
doesn't do it in a way that's morally equivalent to polymorphism, it's
doing it wrong. Also, I'm not that excited about improving polymorphism
in a way that is only accessible with specialized C code. The example of
Oracle-like DECODE() could be handled about as well if we had a second set
of anycompatible-style polymorphic types, that is something like

decode(expr anycompatible,
search1 anycompatible, result1 anycompatible2,
search2 anycompatible, result2 anycompatible2,
search3 anycompatible, result3 anycompatible2,
...
) returns anycompatible2;

With this proposal I can write a good enough implementation of the "decode"
function, although it cannot be 100% compatible. It can cover probably
almost all use cases.

But this design doesn't help with ANSI compatible LEAD, LAG functions.
There is a different strategy - optional argument is implicitly casted to
type of first argument.

Admittedly, you'd need to write a separate declaration for each number of
arguments you wanted to support, but they could all point at the same C
function --- which'd be a lot simpler than in this patch, since it would
not need to deal with any type coercions, only comparisons.

This patch is reduced - first version allowed similar argument list
transformations like parser does with COALESCE or CASE expressions.

When arguments are transformed early, then the body of function can be thin.

I also argue that to the extent that the support function is reinventing
polymorphism internally, it's going to be inferior to the parser's
version. As an example, with Pavel's sample implementation, if a
particular query needs a coercion from type X to type Y, that's nowhere
visible in the parse tree. So you could drop the cast without being told
that view so-and-so depends on it, leading to a run-time failure next time
you try to use that view. Doing the same thing with normal polymorphism,
the X-to-Y cast function would be used in the parse tree and so we'd know
about the dependency.

It is by reduced design. First implementation did a transformation of the
argument list too. Then the cast was visible in the argument list.

It is true, so this patch implements an alternative way to polymorphic
types. I don't think it is necessarily bad (and this functionality is
available only for C language). We do it for COALESCE, CASE, GREATEST,
LEAST functions and minimally due lazy evaluation we don't try to rewrite
these functionality to usual functions. I would not increase the complexity
of Postgres type systems or introduce some specific features used just by
me. When people start to write an application on Postgres, then the current
system is almost good enough. But a different situation is when a
significant factor is compatibility - this is a topic that I have to solve
in Orafce or issue with LAG, LEAD functions. Introducing a special
polymorphic type for some specific behavior is hard and maybe unacceptable
work. For me (as extension author) it can be nice to have some possibility
to modify a parse tree - without useless overhead. With this possibility,
some functions can be lighter and faster - because casting will be outside
the function.

Regards

Pavel

Show quoted text

2. I have no faith that the proposed implementation is correct or
complete. As I complained earlier, a lot of places have special-case
handling for polymorphism, and it seems like every one of them would
need to know about this feature too. That is, to the extent that
this patch's footprint is smaller than commit 24e2885ee -- which it
is, by a lot -- I think those are bugs of omission. It will not work
to have a situation where some parts of the backend resolve a function's
result type as one thing and others resolve it as something else thanks to
failure to account for this new feature. As a concrete example, it looks
like we'd fail pretty hard if someone tried to use this facility in an
aggregate support function.

So my opinion is still what it was in January.

regards, tom lane

[1] /messages/by-id/31501.1579036195@sss.pgh.pa.us

#19David Steele
david@pgmasters.net
In reply to: Pavel Stehule (#18)
Re: proposal: type info support functions for functions that use "any" type

pá 31. 7. 2020 v 2:32 odesílatel Tom Lane <tgl@sss.pgh.pa.us
<mailto:tgl@sss.pgh.pa.us>> napsal:

So my opinion is still what it was in January.

Since there does not appear to be support for this patch and it has not
attracted any new review or comment in the last year I'm planning to
close it on MAR 8 unless there are arguments to the contrary.

Regards,
--
-David
david@pgmasters.net

#20Pavel Stehule
pavel.stehule@gmail.com
In reply to: David Steele (#19)
Re: proposal: type info support functions for functions that use "any" type

st 3. 3. 2021 v 18:12 odesílatel David Steele <david@pgmasters.net> napsal:

pá 31. 7. 2020 v 2:32 odesílatel Tom Lane <tgl@sss.pgh.pa.us
<mailto:tgl@sss.pgh.pa.us>> napsal:

So my opinion is still what it was in January.

Since there does not appear to be support for this patch and it has not
attracted any new review or comment in the last year I'm planning to
close it on MAR 8 unless there are arguments to the contrary.

This feature is very specific. The benefit is mostly for authors of
extensions that try to emulate some other RDBMS. For me - it can reduce
thousands of lines of Orafce source code.

But Tom has a strong negative option on this feature. But on second hand, I
am accepting so this feature is specific and an benefit is specific subset
of users. So there are no strong reasons for hard pushing from me. There
are other similar proposals, so we will see.

Regards

Pavel

Show quoted text

Regards,
--
-David
david@pgmasters.net