summaryrefslogtreecommitdiffstats
path: root/src/test/modules/plsample
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/modules/plsample')
-rw-r--r--src/test/modules/plsample/.gitignore3
-rw-r--r--src/test/modules/plsample/Makefile20
-rw-r--r--src/test/modules/plsample/README6
-rw-r--r--src/test/modules/plsample/expected/plsample.out117
-rw-r--r--src/test/modules/plsample/plsample--1.0.sql14
-rw-r--r--src/test/modules/plsample/plsample.c354
-rw-r--r--src/test/modules/plsample/plsample.control8
-rw-r--r--src/test/modules/plsample/sql/plsample.sql38
8 files changed, 560 insertions, 0 deletions
diff --git a/src/test/modules/plsample/.gitignore b/src/test/modules/plsample/.gitignore
new file mode 100644
index 0000000..44d119c
--- /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 0000000..f1bc334
--- /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 0000000..0ed3193
--- /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
+a simple implementation, yet 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 0000000..8ad5f7a
--- /dev/null
+++ b/src/test/modules/plsample/expected/plsample.out
@@ -0,0 +1,117 @@
+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)
+
+CREATE FUNCTION my_trigger_func() RETURNS trigger AS $$
+if TD_event == "INSERT"
+ return TD_NEW
+elseif TD_event == "UPDATE"
+ return TD_NEW
+else
+ return "OK"
+end
+$$ language plsample;
+CREATE TABLE my_table (num integer, description text);
+CREATE TRIGGER my_trigger_func BEFORE INSERT OR UPDATE ON my_table
+ FOR EACH ROW EXECUTE FUNCTION my_trigger_func();
+CREATE TRIGGER my_trigger_func2 AFTER INSERT OR UPDATE ON my_table
+ FOR EACH ROW EXECUTE FUNCTION my_trigger_func(8);
+INSERT INTO my_table (num, description)
+VALUES (1, 'first');
+NOTICE: source text of function "my_trigger_func":
+if TD_event == "INSERT"
+ return TD_NEW
+elseif TD_event == "UPDATE"
+ return TD_NEW
+else
+ return "OK"
+end
+
+NOTICE: trigger name: my_trigger_func
+NOTICE: trigger relation: my_table
+NOTICE: trigger relation schema: public
+NOTICE: triggered by INSERT
+NOTICE: triggered BEFORE
+NOTICE: triggered per row
+NOTICE: source text of function "my_trigger_func":
+if TD_event == "INSERT"
+ return TD_NEW
+elseif TD_event == "UPDATE"
+ return TD_NEW
+else
+ return "OK"
+end
+
+NOTICE: trigger name: my_trigger_func2
+NOTICE: trigger relation: my_table
+NOTICE: trigger relation schema: public
+NOTICE: triggered by INSERT
+NOTICE: triggered AFTER
+NOTICE: triggered per row
+NOTICE: trigger arg[0]: 8
+UPDATE my_table
+SET description = 'first, modified once'
+WHERE num = 1;
+NOTICE: source text of function "my_trigger_func":
+if TD_event == "INSERT"
+ return TD_NEW
+elseif TD_event == "UPDATE"
+ return TD_NEW
+else
+ return "OK"
+end
+
+NOTICE: trigger name: my_trigger_func
+NOTICE: trigger relation: my_table
+NOTICE: trigger relation schema: public
+NOTICE: triggered by UPDATE
+NOTICE: triggered BEFORE
+NOTICE: triggered per row
+NOTICE: source text of function "my_trigger_func":
+if TD_event == "INSERT"
+ return TD_NEW
+elseif TD_event == "UPDATE"
+ return TD_NEW
+else
+ return "OK"
+end
+
+NOTICE: trigger name: my_trigger_func2
+NOTICE: trigger relation: my_table
+NOTICE: trigger relation schema: public
+NOTICE: triggered by UPDATE
+NOTICE: triggered AFTER
+NOTICE: triggered per row
+NOTICE: trigger arg[0]: 8
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 0000000..fc5b280
--- /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 0000000..780db72
--- /dev/null
+++ b/src/test/modules/plsample/plsample.c
@@ -0,0 +1,354 @@
+/*-------------------------------------------------------------------------
+ *
+ * plsample.c
+ * Handler for the PL/Sample procedural language
+ *
+ * Portions Copyright (c) 1996-2022, 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 "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "commands/event_trigger.h"
+#include "commands/trigger.h"
+#include "executor/spi.h"
+#include "funcapi.h"
+#include "utils/builtins.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);
+static HeapTuple plsample_trigger_handler(PG_FUNCTION_ARGS);
+
+/*
+ * Handle function, procedure, and trigger calls.
+ */
+Datum
+plsample_call_handler(PG_FUNCTION_ARGS)
+{
+ Datum retval = (Datum) 0;
+
+ /*
+ * Many languages will require cleanup that happens even in the event of
+ * an error. That can happen in the PG_FINALLY block. If none is needed,
+ * this PG_TRY construct can be omitted.
+ */
+ 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.
+ */
+ retval = PointerGetDatum(plsample_trigger_handler(fcinfo));
+ }
+ else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
+ {
+ /*
+ * This function is called as an event trigger function, where
+ * (EventTriggerData *) fcinfo->context includes the information
+ * of the context.
+ *
+ * TODO: provide an example handler.
+ */
+ }
+ 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;
+ Oid prorettype;
+ FmgrInfo result_in_func;
+ int numargs;
+
+ /* Fetch the function's pg_proc entry. */
+ 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);
+
+ /*
+ * 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);
+ 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)));
+ }
+
+ /* Type of the result */
+ prorettype = pl_struct->prorettype;
+ ReleaseSysCache(pl_tuple);
+
+ /*
+ * 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 (prorettype != TEXTOID)
+ PG_RETURN_NULL();
+
+ type_tuple = SearchSysCache1(TYPEOID,
+ ObjectIdGetDatum(prorettype));
+ if (!HeapTupleIsValid(type_tuple))
+ elog(ERROR, "cache lookup failed for type %u", 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);
+}
+
+/*
+ * plsample_trigger_handler
+ *
+ * Function called by the call handler for trigger execution.
+ */
+static HeapTuple
+plsample_trigger_handler(PG_FUNCTION_ARGS)
+{
+ TriggerData *trigdata = (TriggerData *) fcinfo->context;
+ char *string;
+ volatile HeapTuple rettup;
+ HeapTuple pl_tuple;
+ Datum ret;
+ char *source;
+ bool isnull;
+ Form_pg_proc pl_struct;
+ char *proname;
+ int rc PG_USED_FOR_ASSERTS_ONLY;
+
+ /* Make sure this is being called from a trigger. */
+ if (!CALLED_AS_TRIGGER(fcinfo))
+ elog(ERROR, "not called by trigger manager");
+
+ /* Connect to the SPI manager */
+ if (SPI_connect() != SPI_OK_CONNECT)
+ elog(ERROR, "could not connect to SPI manager");
+
+ rc = SPI_register_trigger_data(trigdata);
+ Assert(rc >= 0);
+
+ /* Fetch the function's pg_proc entry. */
+ 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);
+
+ /*
+ * Code Retrieval
+ *
+ * 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);
+ source = DatumGetCString(DirectFunctionCall1(textout, ret));
+ ereport(NOTICE,
+ (errmsg("source text of function \"%s\": %s",
+ proname, source)));
+
+ /*
+ * We're done with the pg_proc tuple, so release it. (Note that the
+ * "proname" and "source" strings are now standalone copies.)
+ */
+ ReleaseSysCache(pl_tuple);
+
+ /*
+ * Code Augmentation
+ *
+ * The source text may be augmented here, such as by wrapping it as the
+ * body of a function in the target language, prefixing a parameter list
+ * with names like TD_name, TD_relid, TD_table_name, TD_table_schema,
+ * TD_event, TD_when, TD_level, TD_NEW, TD_OLD, and args, using whatever
+ * types in the target language are convenient. The augmented text can be
+ * cached in a longer-lived memory context, or, if the target language
+ * uses a compilation step, that can be done here, caching the result of
+ * the compilation.
+ */
+
+ /*
+ * Code Execution
+ *
+ * Here the function (the possibly-augmented source text, or the result of
+ * compilation if the target language uses such a step) should be
+ * executed, after binding values from the TriggerData struct to the
+ * appropriate parameters.
+ *
+ * In this example we just print a lot of info via ereport.
+ */
+
+ PG_TRY();
+ {
+ ereport(NOTICE,
+ (errmsg("trigger name: %s", trigdata->tg_trigger->tgname)));
+ string = SPI_getrelname(trigdata->tg_relation);
+ ereport(NOTICE, (errmsg("trigger relation: %s", string)));
+
+ string = SPI_getnspname(trigdata->tg_relation);
+ ereport(NOTICE, (errmsg("trigger relation schema: %s", string)));
+
+ /* Example handling of different trigger aspects. */
+
+ if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
+ {
+ ereport(NOTICE, (errmsg("triggered by INSERT")));
+ rettup = trigdata->tg_trigtuple;
+ }
+ else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
+ {
+ ereport(NOTICE, (errmsg("triggered by DELETE")));
+ rettup = trigdata->tg_trigtuple;
+ }
+ else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+ {
+ ereport(NOTICE, (errmsg("triggered by UPDATE")));
+ rettup = trigdata->tg_trigtuple;
+ }
+ else if (TRIGGER_FIRED_BY_TRUNCATE(trigdata->tg_event))
+ {
+ ereport(NOTICE, (errmsg("triggered by TRUNCATE")));
+ rettup = trigdata->tg_trigtuple;
+ }
+ else
+ elog(ERROR, "unrecognized event: %u", trigdata->tg_event);
+
+ if (TRIGGER_FIRED_BEFORE(trigdata->tg_event))
+ ereport(NOTICE, (errmsg("triggered BEFORE")));
+ else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
+ ereport(NOTICE, (errmsg("triggered AFTER")));
+ else if (TRIGGER_FIRED_INSTEAD(trigdata->tg_event))
+ ereport(NOTICE, (errmsg("triggered INSTEAD OF")));
+ else
+ elog(ERROR, "unrecognized when: %u", trigdata->tg_event);
+
+ if (TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
+ ereport(NOTICE, (errmsg("triggered per row")));
+ else if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
+ ereport(NOTICE, (errmsg("triggered per statement")));
+ else
+ elog(ERROR, "unrecognized level: %u", trigdata->tg_event);
+
+ /*
+ * Iterate through all of the trigger arguments, printing each input
+ * value.
+ */
+ for (int i = 0; i < trigdata->tg_trigger->tgnargs; i++)
+ ereport(NOTICE,
+ (errmsg("trigger arg[%i]: %s", i,
+ trigdata->tg_trigger->tgargs[i])));
+ }
+ PG_CATCH();
+ {
+ /* Error cleanup code would go here */
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ if (SPI_finish() != SPI_OK_FINISH)
+ elog(ERROR, "SPI_finish() failed");
+
+ return rettup;
+}
diff --git a/src/test/modules/plsample/plsample.control b/src/test/modules/plsample/plsample.control
new file mode 100644
index 0000000..1e67251
--- /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 0000000..cf652ad
--- /dev/null
+++ b/src/test/modules/plsample/sql/plsample.sql
@@ -0,0 +1,38 @@
+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}');
+
+CREATE FUNCTION my_trigger_func() RETURNS trigger AS $$
+if TD_event == "INSERT"
+ return TD_NEW
+elseif TD_event == "UPDATE"
+ return TD_NEW
+else
+ return "OK"
+end
+$$ language plsample;
+
+CREATE TABLE my_table (num integer, description text);
+CREATE TRIGGER my_trigger_func BEFORE INSERT OR UPDATE ON my_table
+ FOR EACH ROW EXECUTE FUNCTION my_trigger_func();
+CREATE TRIGGER my_trigger_func2 AFTER INSERT OR UPDATE ON my_table
+ FOR EACH ROW EXECUTE FUNCTION my_trigger_func(8);
+
+INSERT INTO my_table (num, description)
+VALUES (1, 'first');
+
+UPDATE my_table
+SET description = 'first, modified once'
+WHERE num = 1;