mirror of https://github.com/postgres/postgres
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.compull/56/head
parent
6e70443eda
commit
adbe62d04b
@ -0,0 +1,3 @@ |
||||
# Generated subdirectories |
||||
/log/ |
||||
/results/ |
@ -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 |
@ -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. |
@ -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) |
||||
|
@ -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'; |
@ -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); |
||||
} |
@ -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 |
@ -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…
Reference in new issue