From fc105baef0efd80beeb22d8ddc03e78644183294 Mon Sep 17 00:00:00 2001
From: Chapman Flack <chap@anastigmatix.net>
Date: Mon, 21 Feb 2022 21:02:59 -0500
Subject: [PATCH 4/4] PL/Sample "with" TRANSFORM FOR TYPE

Add just enough TRANSFORM handling to PL/Sample for it to announce
what functions would be called (in a real implementation) on which
return or argument types.

Add a test based on creating a completely bogus transform out of
existing functions that have the right parameter and return types
but otherwise no business at all being used for a transform. Because
PL/Sample will not really call them, that will work as a test.

In passing: turn a SearchSysCache(..., 0, 0, 0) to SearchSysCache1
so the example code is following the recommended practice.
---
 src/test/modules/plsample/expected/plsample.out | 31 +++++++++
 src/test/modules/plsample/plsample.c            | 84 ++++++++++++++++++++++++-
 src/test/modules/plsample/sql/plsample.sql      | 18 ++++++
 3 files changed, 131 insertions(+), 2 deletions(-)

diff --git a/src/test/modules/plsample/expected/plsample.out b/src/test/modules/plsample/expected/plsample.out
index a0c318b..0e7850e 100644
--- a/src/test/modules/plsample/expected/plsample.out
+++ b/src/test/modules/plsample/expected/plsample.out
@@ -34,3 +34,34 @@ NOTICE:  argument: 0; name: a1; value: {foo,bar,hoge}
  
 (1 row)
 
+/*
+ * Create a bogus transform using a couple existing functions that
+ * happen to have the right parameter and return types. Calamity does
+ * not ensue, because plsample will merely announce what transform functions
+ * it "would" apply, and not really call them.
+ */
+CREATE TRANSFORM FOR text LANGUAGE plsample (
+  FROM SQL WITH FUNCTION prsd_lextype, TO SQL WITH FUNCTION textrecv
+);
+CREATE OR REPLACE FUNCTION
+  plsample_result_text(a1 numeric, a2 text, a3 integer[])
+RETURNS text
+TRANSFORM FOR TYPE text
+AS $$
+  Example of source with text result.
+$$ LANGUAGE plsample;
+SELECT plsample_result_text(1.23, 'abc', '{4, 5, 6}');
+NOTICE:  source text of function "plsample_result_text": 
+  Example of source with text result.
+
+NOTICE:  argument: 0; name: a1; value: 1.23
+NOTICE:  argument: 1; name: a2; transformed with: prsd_lextype(internal)
+NOTICE:  argument: 2; name: a3; value: {4,5,6}
+NOTICE:  return value: transformed with: textrecv(internal)
+         plsample_result_text          
+---------------------------------------
+                                      +
+   Example of source with text result.+
+ 
+(1 row)
+
diff --git a/src/test/modules/plsample/plsample.c b/src/test/modules/plsample/plsample.c
index 72693f6..7324523 100644
--- a/src/test/modules/plsample/plsample.c
+++ b/src/test/modules/plsample/plsample.c
@@ -22,6 +22,7 @@
 #include "funcapi.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/regproc.h"   /* only for format_procedure() */
 #include "utils/syscache.h"
 
 PG_MODULE_MAGIC;
@@ -92,18 +93,21 @@ plsample_func_handler(PG_FUNCTION_ARGS)
 	Form_pg_proc pl_struct;
 	volatile MemoryContext proc_cxt = NULL;
 	Oid		   *argtypes;
+	List	   *trftypes;
 	char	  **argnames;
 	char	   *argmodes;
 	char	   *proname;
 	Form_pg_type pg_type_entry;
 	Oid			result_typioparam;
 	Oid			prorettype;
+	Oid			prolang;
+	Oid			tosql;
 	FmgrInfo	result_in_func;
 	int			numargs;
 
 	/* Fetch the source text of the function. */
-	pl_tuple = SearchSysCache(PROCOID,
-							  ObjectIdGetDatum(fcinfo->flinfo->fn_oid), 0, 0, 0);
+	pl_tuple = SearchSysCache1(PROCOID,
+							   ObjectIdGetDatum(fcinfo->flinfo->fn_oid));
 	if (!HeapTupleIsValid(pl_tuple))
 		elog(ERROR, "cache lookup failed for function %u",
 			 fcinfo->flinfo->fn_oid);
@@ -113,6 +117,7 @@ plsample_func_handler(PG_FUNCTION_ARGS)
 	 * a base for the function validation and execution.
 	 */
 	pl_struct = (Form_pg_proc) GETSTRUCT(pl_tuple);
+	prolang = pl_struct->prolang;
 	proname = pstrdup(NameStr(pl_struct->proname));
 	ret = SysCacheGetAttr(PROCOID, pl_tuple, Anum_pg_proc_prosrc, &isnull);
 	if (isnull)
@@ -131,26 +136,92 @@ plsample_func_handler(PG_FUNCTION_ARGS)
 									 "PL/Sample function",
 									 ALLOCSET_SMALL_SIZES);
 
+	/*
+	 * This array of FmgrInfo will hold, for each supplied input argument,
+	 * the resolved output-to-text function for that argument's type.
+	 * fcinfo->nargs comes from the caller, and reflects the number of input
+	 * arguments supplied in this call.
+	 */
 	arg_out_func = (FmgrInfo *) palloc0(fcinfo->nargs * sizeof(FmgrInfo));
+
+	/*
+	 * Obtain the types, names, and modes (IN/OUT/INOUT/...) for all of
+	 * the function's statically declared arguments, returning their number
+	 * (giving the size of each result array). numargs can be larger than
+	 * fcinfo->nargs (which only reflects IN arguments). numargs can also be
+	 * smaller than fcinfo->nargs (in the case of a function declared
+	 * VARIADIC "any", but it is rare for a PL to support that).
+	 */
 	numargs = get_func_arg_info(pl_tuple, &argtypes, &argnames, &argmodes);
 
 	/*
+	 * For now, PL/Sample does not support any argument with mode other than IN.
+	 * When mode is IN for every argument, argmodes will be NULL, so report
+	 * an error if it is not. To more helpfully report the error at the time
+	 * of declaration, a validator function can check this too.
+	 */
+	if ( argmodes != NULL )
+		ereport(ERROR,
+				(errmsg("only IN arguments supported for PL/Sample")));
+
+	/*
+	 * Obtain the types for which transforms should be applied.
+	 * For a PL that does not support transforms, it could be considerate to
+	 * report an error here (or earlier, in a validator) if trftypes is not NIL,
+	 * rather than simply ignoring any transforms that might be mistakenly
+	 * declared
+	 */
+	trftypes = get_call_trftypes(pl_tuple);
+
+	/*
 	 * Iterate through all of the function arguments, printing each input
 	 * value.
 	 */
 	for (int i = 0; i < numargs; i++)
 	{
 		Oid			argtype = pl_struct->proargtypes.values[i];
+		Oid			fromsql;
 		char	   *value;
 
+		/*
+		 * Get the Oid of the "from SQL" transform function if this arg's type
+		 * is one of those to which transforms should be applied, or InvalidOid
+		 * if not.
+		 */
+		fromsql = get_transform_fromsql(argtype, prolang, trftypes);
+
 		type_tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(argtype));
 		if (!HeapTupleIsValid(type_tuple))
 			elog(ERROR, "cache lookup failed for type %u", argtype);
 
 		type_struct = (Form_pg_type) GETSTRUCT(type_tuple);
+
+		/*
+		 * Look up the type's output function and save the details
+		 * in arg_out_func[i].
+		 */
 		fmgr_info_cxt(type_struct->typoutput, &(arg_out_func[i]), proc_cxt);
+
 		ReleaseSysCache(type_tuple);
 
+		/*
+		 * If a transform function was found for this arg, use it instead of
+		 * the default conversion below. In this example, "use it" means
+		 * reporting the transform function's name. A real PL would, of course,
+		 * apply that function to the argument value and use the result.
+		 */
+		if ( OidIsValid(fromsql) )
+		{
+			ereport(NOTICE,
+					(errmsg("argument: %d; name: %s; transformed with: %s",
+							i, argnames[i], format_procedure(fromsql))));
+			continue;
+		}
+
+		/*
+		 * No transform, so apply the default conversion that calls the type's
+		 * output function to obtain a string.
+		 */
 		value = OutputFunctionCall(&arg_out_func[i], fcinfo->args[i].value);
 		ereport(NOTICE,
 				(errmsg("argument: %d; name: %s; value: %s",
@@ -172,6 +243,15 @@ plsample_func_handler(PG_FUNCTION_ARGS)
 	if (prorettype != TEXTOID)
 		PG_RETURN_NULL();
 
+	tosql = get_transform_tosql(prorettype, prolang, trftypes);
+
+	if ( OidIsValid(tosql) )
+	{
+		ereport(NOTICE,
+				(errmsg("return value: transformed with: %s",
+						format_procedure(tosql))));
+	}
+
 	type_tuple = SearchSysCache1(TYPEOID,
 								 ObjectIdGetDatum(prorettype));
 	if (!HeapTupleIsValid(type_tuple))
diff --git a/src/test/modules/plsample/sql/plsample.sql b/src/test/modules/plsample/sql/plsample.sql
index bf0fdda..9310cac 100644
--- a/src/test/modules/plsample/sql/plsample.sql
+++ b/src/test/modules/plsample/sql/plsample.sql
@@ -13,3 +13,21 @@ AS $$
   Example of source with void result.
 $$ LANGUAGE plsample;
 SELECT plsample_result_void('{foo, bar, hoge}');
+
+/*
+ * Create a bogus transform using a couple existing functions that
+ * happen to have the right parameter and return types. Calamity does
+ * not ensue, because plsample will merely announce what transform functions
+ * it "would" apply, and not really call them.
+ */
+CREATE TRANSFORM FOR text LANGUAGE plsample (
+  FROM SQL WITH FUNCTION prsd_lextype, TO SQL WITH FUNCTION textrecv
+);
+CREATE OR REPLACE FUNCTION
+  plsample_result_text(a1 numeric, a2 text, a3 integer[])
+RETURNS text
+TRANSFORM FOR TYPE text
+AS $$
+  Example of source with text result.
+$$ LANGUAGE plsample;
+SELECT plsample_result_text(1.23, 'abc', '{4, 5, 6}');
-- 
2.7.3

