On Mon, Jun 15, 2020 at 04:47:01PM +0900, Michael Paquier wrote: > On Sun, Jun 14, 2020 at 08:45:17PM -0700, Mark Wong wrote: > > Sounds good to me. Something more like the attached patch? > > That's the idea. I have not gone in details into what you have here, > but perhaps it would make sense to do a bit more and show how things > are done in the context of a PL function called in a trigger? Your > patch removes from the docs a code block that outlined that.
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. :) Regards, Mark [1] https://commitfest.postgresql.org/29/2678/ -- Mark Wong 2ndQuadrant - PostgreSQL Solutions for the Enterprise https://www.2ndQuadrant.com/
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->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> diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index 29de73c060..95144d8d7c 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -9,6 +9,7 @@ SUBDIRS = \ commit_ts \ dummy_index_am \ dummy_seclabel \ + plsample \ snapshot_too_old \ test_bloomfilter \ test_ddl_deparse \ diff --git a/src/test/modules/plsample/Makefile b/src/test/modules/plsample/Makefile new file mode 100644 index 0000000000..757b47c785 --- /dev/null +++ b/src/test/modules/plsample/Makefile @@ -0,0 +1,20 @@ +# src/test/modules/plsample/Makefile + +PGFILEDESC = "PL/Sample - procedural language" + +REGRESS = create_pl create_func select_func + +EXTENSION = plsample +EXTVERSION = 0.1 + +MODULE_big = plsample + +OBJS = plsample.o + +DATA = plsample.control plsample--0.1.sql + +plsample.o: plsample.c + +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) diff --git a/src/test/modules/plsample/README b/src/test/modules/plsample/README new file mode 100644 index 0000000000..7ee213700b --- /dev/null +++ b/src/test/modules/plsample/README @@ -0,0 +1,3 @@ +plsample is an example procedural-language handler. It is a simple functional +template that demonstrates some of the things that need to be done in order to +build a fully functional procedural-language handler. diff --git a/src/test/modules/plsample/expected/create_func.out b/src/test/modules/plsample/expected/create_func.out new file mode 100644 index 0000000000..df2b915a97 --- /dev/null +++ b/src/test/modules/plsample/expected/create_func.out @@ -0,0 +1,5 @@ +CREATE FUNCTION plsample_func(a1 NUMERIC, a2 TEXT, a3 INTEGER[]) +RETURNS TEXT +AS $$ + This is function's source text. +$$ LANGUAGE plsample; diff --git a/src/test/modules/plsample/expected/create_pl.out b/src/test/modules/plsample/expected/create_pl.out new file mode 100644 index 0000000000..5365391284 --- /dev/null +++ b/src/test/modules/plsample/expected/create_pl.out @@ -0,0 +1,8 @@ +CREATE FUNCTION plsample_call_handler() +RETURNS language_handler +AS '$libdir/plsample' +LANGUAGE C; +CREATE LANGUAGE plsample +HANDLER plsample_call_handler; +COMMENT ON LANGUAGE plsample +IS 'PL/Sample procedural language'; diff --git a/src/test/modules/plsample/expected/select_func.out b/src/test/modules/plsample/expected/select_func.out new file mode 100644 index 0000000000..dc396cbc04 --- /dev/null +++ b/src/test/modules/plsample/expected/select_func.out @@ -0,0 +1,8 @@ +SELECT plsample_func(1.23, 'abc', '{4, 5, 6}'); + plsample_func +----------------------------------- + + + This is function's source text.+ + +(1 row) + diff --git a/src/test/modules/plsample/plsample--0.1.sql b/src/test/modules/plsample/plsample--0.1.sql new file mode 100644 index 0000000000..b429b83ceb --- /dev/null +++ b/src/test/modules/plsample/plsample--0.1.sql @@ -0,0 +1,9 @@ +CREATE FUNCTION plsample_call_handler() +RETURNS language_handler +AS 'MODULE_PATHNAME' +LANGUAGE C; + +CREATE LANGUAGE plsample +HANDLER plsample_call_handler; + +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..69f76e6067 --- /dev/null +++ b/src/test/modules/plsample/plsample.c @@ -0,0 +1,156 @@ +/*------------------------------------------------------------------------- + * + * plsample.c + * Handler for the PL/Sample procedural language + * + * Copyright (c) 2020, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/test/modules/plsample.c + * + *------------------------------------------------------------------------- + */ + +#include <postgres.h> +#include <fmgr.h> +#include <funcapi.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 <utils/builtins.h> +#include <utils/elog.h> +#include <utils/memutils.h> +#include <utils/lsyscache.h> +#include <utils/syscache.h> + +MemoryContext TopMemoryContext = NULL; + +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)) + { + /* TODO: Invoke the trigger handler. */ + } + else if (CALLED_AS_EVENT_TRIGGER(fcinfo)) + { + /* TODO: Invoke the event trigger handler. */ + } + else + { + /* Invoke the regular function handler. */ + retval = plsample_func_handler(fcinfo); + } + } + PG_FINALLY(); + { + } + PG_END_TRY(); + return retval; +} + +/* Handler for regular function and stored procedure calls. */ +static Datum +plsample_func_handler(PG_FUNCTION_ARGS) +{ + HeapTuple pl_tuple; + Datum ret; + char *source; + bool isnull; + + int i; + 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 *value; + + Form_pg_type pg_type_entry; + Oid result_typioparam; + FmgrInfo result_in_func; + + /* Fetch the source 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); + pl_struct = (Form_pg_proc) GETSTRUCT(pl_tuple); + + ret = SysCacheGetAttr(PROCOID, pl_tuple, Anum_pg_proc_prosrc, &isnull); + if (isnull) + elog(ERROR, "null prosrc"); + ReleaseSysCache(pl_tuple); + + source = DatumGetCString(DirectFunctionCall1(textout, ret)); + elog(LOG, "source text:\n%s", source); + + arg_out_func = (FmgrInfo *) palloc0(fcinfo->nargs * sizeof(FmgrInfo)); + proc_cxt = AllocSetContextCreate(TopMemoryContext, + "PL/Sample function", 0, (1 * 1024), (8 * 1024)); + get_func_arg_info(pl_tuple, &argtypes, &argnames, &argmodes); + + /* Iterate through all of the function arguments. */ + elog(LOG, "number of arguments : %d", fcinfo->nargs); + for (i = 0; i < fcinfo->nargs; i++) + { + Oid argtype = pl_struct->proargtypes.values[i]; + 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); + + elog(LOG, "argument position: %d; name: %s; value: %s", i, argnames[i], + value); + } + + /* Fetch the return type of the function. */ + + 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); + + proc_cxt = AllocSetContextCreate(TopMemoryContext, "PL/Sample function", + ALLOCSET_SMALL_SIZES); + + result_typioparam = getTypeIOParam(type_tuple); + + fmgr_info_cxt(pg_type_entry->typinput, &result_in_func, proc_cxt); + ReleaseSysCache(type_tuple); + + /* Simply return the function source text. */ + 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..dba58e5abf --- /dev/null +++ b/src/test/modules/plsample/plsample.control @@ -0,0 +1,7 @@ +# plsample extension +comment = 'PL/Sample' +default_version = '0.1' +module_pathname = '$libdir/plsample' +relocatable = false +schema = pg_catalog +superuser = false diff --git a/src/test/modules/plsample/sql/create_func.sql b/src/test/modules/plsample/sql/create_func.sql new file mode 100644 index 0000000000..df2b915a97 --- /dev/null +++ b/src/test/modules/plsample/sql/create_func.sql @@ -0,0 +1,5 @@ +CREATE FUNCTION plsample_func(a1 NUMERIC, a2 TEXT, a3 INTEGER[]) +RETURNS TEXT +AS $$ + This is function's source text. +$$ LANGUAGE plsample; diff --git a/src/test/modules/plsample/sql/create_pl.sql b/src/test/modules/plsample/sql/create_pl.sql new file mode 100644 index 0000000000..c3ace0f1aa --- /dev/null +++ b/src/test/modules/plsample/sql/create_pl.sql @@ -0,0 +1,10 @@ +CREATE FUNCTION plsample_call_handler() +RETURNS language_handler +AS '$libdir/plsample' +LANGUAGE C; + +CREATE LANGUAGE plsample +HANDLER plsample_call_handler; + +COMMENT ON LANGUAGE plsample +IS 'PL/Sample procedural language'; diff --git a/src/test/modules/plsample/sql/select_func.sql b/src/test/modules/plsample/sql/select_func.sql new file mode 100644 index 0000000000..5ded186984 --- /dev/null +++ b/src/test/modules/plsample/sql/select_func.sql @@ -0,0 +1 @@ +SELECT plsample_func(1.23, 'abc', '{4, 5, 6}');