Add PL/Sample to src/test/modules/
PL/Sample is an example template of procedural-language handler. This can be used as a base to implement a custom PL, or as a facility to test APIs dedicated to PLs. Much more could be done in this module, like adding a simple validator, but this is left as future work. The documentation included originally some C code to understand the basics of PL handler implementation, but it was outdated, and not really helpful either if trying to implement a new procedural language, particularly when it came to the integration of a PL installation with CREATE EXTENSION. Author: Mark Wong Reviewed-by: Tom Lane, Michael Paquier Discussion: https://postgr.es/m/20200612172648.GA3327@2ndQuadrant.com
This commit is contained in:
parent
6e70443eda
commit
adbe62d04b
doc/src/sgml
src/test/modules
@ -96,62 +96,10 @@
|
||||
</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.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
|
@ -10,6 +10,7 @@ SUBDIRS = \
|
||||
delay_execution \
|
||||
dummy_index_am \
|
||||
dummy_seclabel \
|
||||
plsample \
|
||||
snapshot_too_old \
|
||||
test_bloomfilter \
|
||||
test_ddl_deparse \
|
||||
|
3
src/test/modules/plsample/.gitignore
vendored
Normal file
3
src/test/modules/plsample/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# Generated subdirectories
|
||||
/log/
|
||||
/results/
|
20
src/test/modules/plsample/Makefile
Normal file
20
src/test/modules/plsample/Makefile
Normal file
@ -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
|
6
src/test/modules/plsample/README
Normal file
6
src/test/modules/plsample/README
Normal file
@ -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.
|
36
src/test/modules/plsample/expected/plsample.out
Normal file
36
src/test/modules/plsample/expected/plsample.out
Normal file
@ -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)
|
||||
|
14
src/test/modules/plsample/plsample--1.0.sql
Normal file
14
src/test/modules/plsample/plsample--1.0.sql
Normal file
@ -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';
|
183
src/test/modules/plsample/plsample.c
Normal file
183
src/test/modules/plsample/plsample.c
Normal file
@ -0,0 +1,183 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* 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 "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/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 includes 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);
|
||||
}
|
8
src/test/modules/plsample/plsample.control
Normal file
8
src/test/modules/plsample/plsample.control
Normal file
@ -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
|
15
src/test/modules/plsample/sql/plsample.sql
Normal file
15
src/test/modules/plsample/sql/plsample.sql
Normal file
@ -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}');
|
Loading…
x
Reference in New Issue
Block a user