Hi

so 9. 3. 2019 v 7:22 odesílatel Pavel Stehule <pavel.steh...@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


>
> Regards
>
> Pavel
>
>
>
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 */

Reply via email to