On Tue, Aug 11, 2020 at 01:01:10PM -0700, Mark Wong wrote:
> Ah, right.  For the moment I've added some empty conditionals for
> trigger and event trigger handling.
> 
> I've created a new entry in the commitfest app. [1]  I'll keep at it. :)

Thanks for the patch.  I have reviewed and reworked it as the
attached.  Some comments below.

+PGFILEDESC = "PL/Sample - procedural language"
+
+REGRESS = create_pl create_func select_func
+
+EXTENSION = plsample
+EXTVERSION = 0.1

This makefile has a couple of mistakes, and can be simplified a lot:
- make check does not work, as you forgot a PGXS part.
- MODULES can just be used as there is only one file (forgot WIN32RES
in OBJS for example)
- DATA does not need the .control file.

.gitignore was missing.

We could just use 1.0 instead of 0.1 for the version number.  That's
not a big deal one way or another, but 1.0 is more consistent with the
other modules.

plsample--1.0.sql should complain if attempting to load the file from
psql.  Also I have cleaned up the README.

Not sure that there is a point in having three different files for the
regression tests.  create_pl.sql is actually not necessary as you
can do the same with CREATE EXTENSION.

The header list of plsample.c was inconsistent with the style used
normally in modules, and I have extended a bit the handler function so
as we return a result only if the return type of the procedure is text
for the source text of the function, tweaked the results a bit, etc.
There was a family of small issues, like using ALLOCSET_SMALL_SIZES
for the context creation.  We could of course expand the sample
handler more in the future to check for pseudotype results, have a
validator, but that could happen later, if necessary.
--
Michael
From fb017d6277a76653385ae7179307177d20dbe194 Mon Sep 17 00:00:00 2001
From: Michael Paquier <mich...@paquier.xyz>
Date: Fri, 14 Aug 2020 14:24:15 +0900
Subject: [PATCH] Add PL/sample to src/test/modules/

---
 src/test/modules/Makefile                     |   1 +
 src/test/modules/plsample/.gitignore          |   3 +
 src/test/modules/plsample/Makefile            |  20 ++
 src/test/modules/plsample/README              |   6 +
 .../modules/plsample/expected/plsample.out    |  36 ++++
 src/test/modules/plsample/plsample--1.0.sql   |  14 ++
 src/test/modules/plsample/plsample.c          | 186 ++++++++++++++++++
 src/test/modules/plsample/plsample.control    |   8 +
 src/test/modules/plsample/sql/plsample.sql    |  15 ++
 doc/src/sgml/plhandler.sgml                   |  62 +-----
 10 files changed, 295 insertions(+), 56 deletions(-)
 create mode 100644 src/test/modules/plsample/.gitignore
 create mode 100644 src/test/modules/plsample/Makefile
 create mode 100644 src/test/modules/plsample/README
 create mode 100644 src/test/modules/plsample/expected/plsample.out
 create mode 100644 src/test/modules/plsample/plsample--1.0.sql
 create mode 100644 src/test/modules/plsample/plsample.c
 create mode 100644 src/test/modules/plsample/plsample.control
 create mode 100644 src/test/modules/plsample/sql/plsample.sql

diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 1428529b04..a6d2ffbf9e 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -10,6 +10,7 @@ SUBDIRS = \
 		  delay_execution \
 		  dummy_index_am \
 		  dummy_seclabel \
+		  plsample \
 		  snapshot_too_old \
 		  test_bloomfilter \
 		  test_ddl_deparse \
diff --git a/src/test/modules/plsample/.gitignore b/src/test/modules/plsample/.gitignore
new file mode 100644
index 0000000000..44d119cfcc
--- /dev/null
+++ b/src/test/modules/plsample/.gitignore
@@ -0,0 +1,3 @@
+# Generated subdirectories
+/log/
+/results/
diff --git a/src/test/modules/plsample/Makefile b/src/test/modules/plsample/Makefile
new file mode 100644
index 0000000000..f1bc334bfc
--- /dev/null
+++ b/src/test/modules/plsample/Makefile
@@ -0,0 +1,20 @@
+# src/test/modules/plsample/Makefile
+
+MODULES = plsample
+
+EXTENSION = plsample
+DATA = plsample--1.0.sql
+PGFILEDESC = "PL/Sample - template for procedural language"
+
+REGRESS = plsample
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/plsample
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/plsample/README b/src/test/modules/plsample/README
new file mode 100644
index 0000000000..7d44d7b3f2
--- /dev/null
+++ b/src/test/modules/plsample/README
@@ -0,0 +1,6 @@
+PL/Sample
+=========
+
+PL/Sample is an example template of procedural-language handler.  It is
+kept a maximum simple, and demonstrates some of the things that can be done
+to build a fully functional procedural-language handler.
diff --git a/src/test/modules/plsample/expected/plsample.out b/src/test/modules/plsample/expected/plsample.out
new file mode 100644
index 0000000000..a0c318b6df
--- /dev/null
+++ b/src/test/modules/plsample/expected/plsample.out
@@ -0,0 +1,36 @@
+CREATE EXTENSION plsample;
+-- Create and test some dummy functions
+CREATE FUNCTION plsample_result_text(a1 numeric, a2 text, a3 integer[])
+RETURNS 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; value: abc
+NOTICE:  argument: 2; name: a3; value: {4,5,6}
+         plsample_result_text          
+---------------------------------------
+                                      +
+   Example of source with text result.+
+ 
+(1 row)
+
+CREATE FUNCTION plsample_result_void(a1 text[])
+RETURNS VOID
+AS $$
+  Example of source with void result.
+$$ LANGUAGE plsample;
+SELECT plsample_result_void('{foo, bar, hoge}');
+NOTICE:  source text of function "plsample_result_void": 
+  Example of source with void result.
+
+NOTICE:  argument: 0; name: a1; value: {foo,bar,hoge}
+ plsample_result_void 
+----------------------
+ 
+(1 row)
+
diff --git a/src/test/modules/plsample/plsample--1.0.sql b/src/test/modules/plsample/plsample--1.0.sql
new file mode 100644
index 0000000000..fc5b280bd4
--- /dev/null
+++ b/src/test/modules/plsample/plsample--1.0.sql
@@ -0,0 +1,14 @@
+/* src/test/modules/plsample/plsample--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION plsample" to load this file. \quit
+
+CREATE FUNCTION plsample_call_handler() RETURNS language_handler
+  AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE TRUSTED LANGUAGE plsample
+  HANDLER plsample_call_handler;
+
+ALTER LANGUAGE plsample OWNER TO @extowner@;
+
+COMMENT ON LANGUAGE plsample IS 'PL/Sample procedural language';
diff --git a/src/test/modules/plsample/plsample.c b/src/test/modules/plsample/plsample.c
new file mode 100644
index 0000000000..41d02105fd
--- /dev/null
+++ b/src/test/modules/plsample/plsample.c
@@ -0,0 +1,186 @@
+/*-------------------------------------------------------------------------
+ *
+ * plsample.c
+ *	  Handler for the PL/Sample procedural language
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *		src/test/modules/plsample/plsample.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "commands/event_trigger.h"
+#include "commands/trigger.h"
+#include "funcapi.h"
+#include "utils/builtins.h"
+#include "utils/elog.h"
+#include "utils/memutils.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(plsample_call_handler);
+
+static Datum plsample_func_handler(PG_FUNCTION_ARGS);
+
+/*
+ * Handle function, procedure, and trigger calls.
+ */
+Datum
+plsample_call_handler(PG_FUNCTION_ARGS)
+{
+	Datum		retval = (Datum) 0;
+
+	PG_TRY();
+	{
+		/*
+		 * Determine if called as function or trigger and call appropriate
+		 * subhandler.
+		 */
+		if (CALLED_AS_TRIGGER(fcinfo))
+		{
+			/*
+			 * This function has been called as a trigger function, where
+			 * (TriggerData *) fcinfo->context includes the information of the
+			 * context.
+			 */
+		}
+		else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
+		{
+			/*
+			 * This function is called as an event trigger function, where
+			 * (EventTriggerData *) fcinfo->context include the information of
+			 * the context.
+			 */
+		}
+		else
+		{
+			/* Regular function handler */
+			retval = plsample_func_handler(fcinfo);
+		}
+	}
+	PG_FINALLY();
+	{
+	}
+	PG_END_TRY();
+
+	return retval;
+}
+
+/*
+ * plsample_func_handler
+ *
+ * Function called by the call handler for function execution.
+ */
+static Datum
+plsample_func_handler(PG_FUNCTION_ARGS)
+{
+	HeapTuple	pl_tuple;
+	Datum		ret;
+	char	   *source;
+	bool		isnull;
+	FmgrInfo   *arg_out_func;
+	Form_pg_type type_struct;
+	HeapTuple	type_tuple;
+	Form_pg_proc pl_struct;
+	volatile MemoryContext proc_cxt = NULL;
+	Oid		   *argtypes;
+	char	  **argnames;
+	char	   *argmodes;
+	char	   *proname;
+	Form_pg_type pg_type_entry;
+	Oid			result_typioparam;
+	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);
+	if (!HeapTupleIsValid(pl_tuple))
+		elog(ERROR, "cache lookup failed for function %u",
+			 fcinfo->flinfo->fn_oid);
+
+	/*
+	 * Extract and print the source text of the function.  This can be used as
+	 * a base for the function validation and execution.
+	 */
+	pl_struct = (Form_pg_proc) GETSTRUCT(pl_tuple);
+	proname = pstrdup(NameStr(pl_struct->proname));
+	ret = SysCacheGetAttr(PROCOID, pl_tuple, Anum_pg_proc_prosrc, &isnull);
+	if (isnull)
+		elog(ERROR, "could not find source text of function \"%s\"",
+			 proname);
+	ReleaseSysCache(pl_tuple);
+	source = DatumGetCString(DirectFunctionCall1(textout, ret));
+	ereport(NOTICE,
+			(errmsg("source text of function \"%s\": %s",
+					proname, source)));
+
+	/*
+	 * Allocate a context that will hold all the Postgres data for the
+	 * procedure.
+	 */
+	proc_cxt = AllocSetContextCreate(TopMemoryContext,
+									 "PL/Sample function",
+									 ALLOCSET_SMALL_SIZES);
+
+	arg_out_func = (FmgrInfo *) palloc0(fcinfo->nargs * sizeof(FmgrInfo));
+	numargs = get_func_arg_info(pl_tuple, &argtypes, &argnames, &argmodes);
+
+	/*
+	 * 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];
+		char	   *value;
+
+		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);
+		fmgr_info_cxt(type_struct->typoutput, &(arg_out_func[i]), proc_cxt);
+		ReleaseSysCache(type_tuple);
+
+		value = OutputFunctionCall(&arg_out_func[i], fcinfo->args[i].value);
+		ereport(NOTICE,
+				(errmsg("argument: %d; name: %s; value: %s",
+						i, argnames[i], value)));
+	}
+
+	/*
+	 * Get the required information for input conversion of the return value.
+	 *
+	 * If the function uses VOID as result, it is better to return NULL.
+	 * Anyway, let's be honest.  This is just a template, so there is not much
+	 * we can do here.  This returns NULL except if the result type is text,
+	 * where the result is the source text of the function.
+	 */
+	if (pl_struct->prorettype != TEXTOID)
+		PG_RETURN_NULL();
+
+	type_tuple = SearchSysCache1(TYPEOID,
+								 ObjectIdGetDatum(pl_struct->prorettype));
+	if (!HeapTupleIsValid(type_tuple))
+		elog(ERROR, "cache lookup failed for type %u", pl_struct->prorettype);
+	pg_type_entry = (Form_pg_type) GETSTRUCT(type_tuple);
+	result_typioparam = getTypeIOParam(type_tuple);
+
+	fmgr_info_cxt(pg_type_entry->typinput, &result_in_func, proc_cxt);
+	ReleaseSysCache(type_tuple);
+
+	ret = InputFunctionCall(&result_in_func, source, result_typioparam, -1);
+	PG_RETURN_DATUM(ret);
+}
diff --git a/src/test/modules/plsample/plsample.control b/src/test/modules/plsample/plsample.control
new file mode 100644
index 0000000000..1e67251a1e
--- /dev/null
+++ b/src/test/modules/plsample/plsample.control
@@ -0,0 +1,8 @@
+# plsample extension
+comment = 'PL/Sample'
+default_version = '1.0'
+module_pathname = '$libdir/plsample'
+relocatable = false
+schema = pg_catalog
+superuser = false
+trusted = true
diff --git a/src/test/modules/plsample/sql/plsample.sql b/src/test/modules/plsample/sql/plsample.sql
new file mode 100644
index 0000000000..bf0fddac7f
--- /dev/null
+++ b/src/test/modules/plsample/sql/plsample.sql
@@ -0,0 +1,15 @@
+CREATE EXTENSION plsample;
+-- Create and test some dummy functions
+CREATE FUNCTION plsample_result_text(a1 numeric, a2 text, a3 integer[])
+RETURNS TEXT
+AS $$
+  Example of source with text result.
+$$ LANGUAGE plsample;
+SELECT plsample_result_text(1.23, 'abc', '{4, 5, 6}');
+
+CREATE FUNCTION plsample_result_void(a1 text[])
+RETURNS VOID
+AS $$
+  Example of source with void result.
+$$ LANGUAGE plsample;
+SELECT plsample_result_void('{foo, bar, hoge}');
diff --git a/doc/src/sgml/plhandler.sgml b/doc/src/sgml/plhandler.sgml
index e1b0af7a60..7b2c5624c0 100644
--- a/doc/src/sgml/plhandler.sgml
+++ b/doc/src/sgml/plhandler.sgml
@@ -96,62 +96,12 @@
    </para>
 
    <para>
-    This is a template for a procedural-language handler written in C:
-<programlisting>
-#include "postgres.h"
-#include "executor/spi.h"
-#include "commands/trigger.h"
-#include "fmgr.h"
-#include "access/heapam.h"
-#include "utils/syscache.h"
-#include "catalog/pg_proc.h"
-#include "catalog/pg_type.h"
-
-PG_MODULE_MAGIC;
-
-PG_FUNCTION_INFO_V1(plsample_call_handler);
-
-Datum
-plsample_call_handler(PG_FUNCTION_ARGS)
-{
-    Datum          retval;
-
-    if (CALLED_AS_TRIGGER(fcinfo))
-    {
-        /*
-         * Called as a trigger function
-         */
-        TriggerData    *trigdata = (TriggerData *) fcinfo-&gt;context;
-
-        retval = ...
-    }
-    else
-    {
-        /*
-         * Called as a function
-         */
-
-        retval = ...
-    }
-
-    return retval;
-}
-</programlisting>
-    Only a few thousand lines of code have to be added instead of the
-    dots to complete the call handler.
-   </para>
-
-   <para>
-    After having compiled the handler function into a loadable module
-    (see <xref linkend="dfunc"/>), the following commands then
-    register the sample procedural language:
-<programlisting>
-CREATE FUNCTION plsample_call_handler() RETURNS language_handler
-    AS '<replaceable>filename</replaceable>'
-    LANGUAGE C;
-CREATE LANGUAGE plsample
-    HANDLER plsample_call_handler;
-</programlisting>
+    A template for a procedural-language handler written as a C extension is
+    provided in <literal>src/test/modules/plsample</literal>.  This is a
+    working sample demonstrating one way to create a procedural-language
+    handler, process parameters, and return a value.  A few thousand lines of
+    additional code may have to be added to complete a fully functional
+    handler.
    </para>
 
    <para>
-- 
2.28.0

Attachment: signature.asc
Description: PGP signature

Reply via email to