Skip to content

Commit 2f4d0d6

Browse files
committed
Extend plsample example to include a trigger handler.
Mark Wong and Konstantina Skovola, reviewed by Chapman Flack Discussion: https://postgr.es/m/Yd8Cz22eHi80XS30@workstation-mark-wong
1 parent 9f8a050 commit 2f4d0d6

File tree

3 files changed

+274
-3
lines changed

3 files changed

+274
-3
lines changed

src/test/modules/plsample/expected/plsample.out

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,84 @@ NOTICE: argument: 0; name: a1; value: {foo,bar,hoge}
3434

3535
(1 row)
3636

37+
CREATE FUNCTION my_trigger_func() RETURNS trigger AS $$
38+
if TD_event == "INSERT"
39+
return TD_NEW
40+
elseif TD_event == "UPDATE"
41+
return TD_NEW
42+
else
43+
return "OK"
44+
end
45+
$$ language plsample;
46+
CREATE TABLE my_table (num integer, description text);
47+
CREATE TRIGGER my_trigger_func BEFORE INSERT OR UPDATE ON my_table
48+
FOR EACH ROW EXECUTE FUNCTION my_trigger_func();
49+
CREATE TRIGGER my_trigger_func2 AFTER INSERT OR UPDATE ON my_table
50+
FOR EACH ROW EXECUTE FUNCTION my_trigger_func(8);
51+
INSERT INTO my_table (num, description)
52+
VALUES (1, 'first');
53+
NOTICE: source text of function "my_trigger_func":
54+
if TD_event == "INSERT"
55+
return TD_NEW
56+
elseif TD_event == "UPDATE"
57+
return TD_NEW
58+
else
59+
return "OK"
60+
end
61+
62+
NOTICE: trigger name: my_trigger_func
63+
NOTICE: trigger relation: my_table
64+
NOTICE: trigger relation schema: public
65+
NOTICE: triggered by INSERT
66+
NOTICE: triggered BEFORE
67+
NOTICE: triggered per row
68+
NOTICE: source text of function "my_trigger_func":
69+
if TD_event == "INSERT"
70+
return TD_NEW
71+
elseif TD_event == "UPDATE"
72+
return TD_NEW
73+
else
74+
return "OK"
75+
end
76+
77+
NOTICE: trigger name: my_trigger_func2
78+
NOTICE: trigger relation: my_table
79+
NOTICE: trigger relation schema: public
80+
NOTICE: triggered by INSERT
81+
NOTICE: triggered AFTER
82+
NOTICE: triggered per row
83+
NOTICE: trigger arg[0]: 8
84+
UPDATE my_table
85+
SET description = 'first, modified once'
86+
WHERE num = 1;
87+
NOTICE: source text of function "my_trigger_func":
88+
if TD_event == "INSERT"
89+
return TD_NEW
90+
elseif TD_event == "UPDATE"
91+
return TD_NEW
92+
else
93+
return "OK"
94+
end
95+
96+
NOTICE: trigger name: my_trigger_func
97+
NOTICE: trigger relation: my_table
98+
NOTICE: trigger relation schema: public
99+
NOTICE: triggered by UPDATE
100+
NOTICE: triggered BEFORE
101+
NOTICE: triggered per row
102+
NOTICE: source text of function "my_trigger_func":
103+
if TD_event == "INSERT"
104+
return TD_NEW
105+
elseif TD_event == "UPDATE"
106+
return TD_NEW
107+
else
108+
return "OK"
109+
end
110+
111+
NOTICE: trigger name: my_trigger_func2
112+
NOTICE: trigger relation: my_table
113+
NOTICE: trigger relation schema: public
114+
NOTICE: triggered by UPDATE
115+
NOTICE: triggered AFTER
116+
NOTICE: triggered per row
117+
NOTICE: trigger arg[0]: 8

src/test/modules/plsample/plsample.c

Lines changed: 170 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "catalog/pg_type.h"
2020
#include "commands/event_trigger.h"
2121
#include "commands/trigger.h"
22+
#include "executor/spi.h"
2223
#include "funcapi.h"
2324
#include "utils/builtins.h"
2425
#include "utils/lsyscache.h"
@@ -29,6 +30,7 @@ PG_MODULE_MAGIC;
2930
PG_FUNCTION_INFO_V1(plsample_call_handler);
3031

3132
static Datum plsample_func_handler(PG_FUNCTION_ARGS);
33+
static HeapTuple plsample_trigger_handler(PG_FUNCTION_ARGS);
3234

3335
/*
3436
* Handle function, procedure, and trigger calls.
@@ -38,6 +40,11 @@ plsample_call_handler(PG_FUNCTION_ARGS)
3840
{
3941
Datum retval = (Datum) 0;
4042

43+
/*
44+
* Many languages will require cleanup that happens even in the event of
45+
* an error. That can happen in the PG_FINALLY block. If none is needed,
46+
* this PG_TRY construct can be omitted.
47+
*/
4148
PG_TRY();
4249
{
4350
/*
@@ -51,13 +58,16 @@ plsample_call_handler(PG_FUNCTION_ARGS)
5158
* (TriggerData *) fcinfo->context includes the information of the
5259
* context.
5360
*/
61+
retval = PointerGetDatum(plsample_trigger_handler(fcinfo));
5462
}
5563
else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
5664
{
5765
/*
5866
* This function is called as an event trigger function, where
5967
* (EventTriggerData *) fcinfo->context includes the information
6068
* of the context.
69+
*
70+
* TODO: provide an example handler.
6171
*/
6272
}
6373
else
@@ -101,9 +111,9 @@ plsample_func_handler(PG_FUNCTION_ARGS)
101111
FmgrInfo result_in_func;
102112
int numargs;
103113

104-
/* Fetch the source text of the function. */
105-
pl_tuple = SearchSysCache(PROCOID,
106-
ObjectIdGetDatum(fcinfo->flinfo->fn_oid), 0, 0, 0);
114+
/* Fetch the function's pg_proc entry. */
115+
pl_tuple = SearchSysCache1(PROCOID,
116+
ObjectIdGetDatum(fcinfo->flinfo->fn_oid));
107117
if (!HeapTupleIsValid(pl_tuple))
108118
elog(ERROR, "cache lookup failed for function %u",
109119
fcinfo->flinfo->fn_oid);
@@ -185,3 +195,160 @@ plsample_func_handler(PG_FUNCTION_ARGS)
185195
ret = InputFunctionCall(&result_in_func, source, result_typioparam, -1);
186196
PG_RETURN_DATUM(ret);
187197
}
198+
199+
/*
200+
* plsample_trigger_handler
201+
*
202+
* Function called by the call handler for trigger execution.
203+
*/
204+
static HeapTuple
205+
plsample_trigger_handler(PG_FUNCTION_ARGS)
206+
{
207+
TriggerData *trigdata = (TriggerData *) fcinfo->context;
208+
char *string;
209+
volatile HeapTuple rettup;
210+
HeapTuple pl_tuple;
211+
Datum ret;
212+
char *source;
213+
bool isnull;
214+
Form_pg_proc pl_struct;
215+
char *proname;
216+
int rc PG_USED_FOR_ASSERTS_ONLY;
217+
218+
/* Make sure this is being called from a trigger. */
219+
if (!CALLED_AS_TRIGGER(fcinfo))
220+
elog(ERROR, "not called by trigger manager");
221+
222+
/* Connect to the SPI manager */
223+
if (SPI_connect() != SPI_OK_CONNECT)
224+
elog(ERROR, "could not connect to SPI manager");
225+
226+
rc = SPI_register_trigger_data(trigdata);
227+
Assert(rc >= 0);
228+
229+
/* Fetch the function's pg_proc entry. */
230+
pl_tuple = SearchSysCache1(PROCOID,
231+
ObjectIdGetDatum(fcinfo->flinfo->fn_oid));
232+
if (!HeapTupleIsValid(pl_tuple))
233+
elog(ERROR, "cache lookup failed for function %u",
234+
fcinfo->flinfo->fn_oid);
235+
236+
/*
237+
* Code Retrieval
238+
*
239+
* Extract and print the source text of the function. This can be used as
240+
* a base for the function validation and execution.
241+
*/
242+
pl_struct = (Form_pg_proc) GETSTRUCT(pl_tuple);
243+
proname = pstrdup(NameStr(pl_struct->proname));
244+
ret = SysCacheGetAttr(PROCOID, pl_tuple, Anum_pg_proc_prosrc, &isnull);
245+
if (isnull)
246+
elog(ERROR, "could not find source text of function \"%s\"",
247+
proname);
248+
source = DatumGetCString(DirectFunctionCall1(textout, ret));
249+
ereport(NOTICE,
250+
(errmsg("source text of function \"%s\": %s",
251+
proname, source)));
252+
253+
/*
254+
* We're done with the pg_proc tuple, so release it. (Note that the
255+
* "proname" and "source" strings are now standalone copies.)
256+
*/
257+
ReleaseSysCache(pl_tuple);
258+
259+
/*
260+
* Code Augmentation
261+
*
262+
* The source text may be augmented here, such as by wrapping it as the
263+
* body of a function in the target language, prefixing a parameter list
264+
* with names like TD_name, TD_relid, TD_table_name, TD_table_schema,
265+
* TD_event, TD_when, TD_level, TD_NEW, TD_OLD, and args, using whatever
266+
* types in the target language are convenient. The augmented text can be
267+
* cached in a longer-lived memory context, or, if the target language
268+
* uses a compilation step, that can be done here, caching the result of
269+
* the compilation.
270+
*/
271+
272+
/*
273+
* Code Execution
274+
*
275+
* Here the function (the possibly-augmented source text, or the result of
276+
* compilation if the target language uses such a step) should be
277+
* executed, after binding values from the TriggerData struct to the
278+
* appropriate parameters.
279+
*
280+
* In this example we just print a lot of info via ereport.
281+
*/
282+
283+
PG_TRY();
284+
{
285+
ereport(NOTICE,
286+
(errmsg("trigger name: %s", trigdata->tg_trigger->tgname)));
287+
string = SPI_getrelname(trigdata->tg_relation);
288+
ereport(NOTICE, (errmsg("trigger relation: %s", string)));
289+
290+
string = SPI_getnspname(trigdata->tg_relation);
291+
ereport(NOTICE, (errmsg("trigger relation schema: %s", string)));
292+
293+
/* Example handling of different trigger aspects. */
294+
295+
if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
296+
{
297+
ereport(NOTICE, (errmsg("triggered by INSERT")));
298+
rettup = trigdata->tg_trigtuple;
299+
}
300+
else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
301+
{
302+
ereport(NOTICE, (errmsg("triggered by DELETE")));
303+
rettup = trigdata->tg_trigtuple;
304+
}
305+
else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
306+
{
307+
ereport(NOTICE, (errmsg("triggered by UPDATE")));
308+
rettup = trigdata->tg_trigtuple;
309+
}
310+
else if (TRIGGER_FIRED_BY_TRUNCATE(trigdata->tg_event))
311+
{
312+
ereport(NOTICE, (errmsg("triggered by TRUNCATE")));
313+
rettup = trigdata->tg_trigtuple;
314+
}
315+
else
316+
elog(ERROR, "unrecognized event: %u", trigdata->tg_event);
317+
318+
if (TRIGGER_FIRED_BEFORE(trigdata->tg_event))
319+
ereport(NOTICE, (errmsg("triggered BEFORE")));
320+
else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
321+
ereport(NOTICE, (errmsg("triggered AFTER")));
322+
else if (TRIGGER_FIRED_INSTEAD(trigdata->tg_event))
323+
ereport(NOTICE, (errmsg("triggered INSTEAD OF")));
324+
else
325+
elog(ERROR, "unrecognized when: %u", trigdata->tg_event);
326+
327+
if (TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
328+
ereport(NOTICE, (errmsg("triggered per row")));
329+
else if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
330+
ereport(NOTICE, (errmsg("triggered per statement")));
331+
else
332+
elog(ERROR, "unrecognized level: %u", trigdata->tg_event);
333+
334+
/*
335+
* Iterate through all of the trigger arguments, printing each input
336+
* value.
337+
*/
338+
for (int i = 0; i < trigdata->tg_trigger->tgnargs; i++)
339+
ereport(NOTICE,
340+
(errmsg("trigger arg[%i]: %s", i,
341+
trigdata->tg_trigger->tgargs[i])));
342+
}
343+
PG_CATCH();
344+
{
345+
/* Error cleanup code would go here */
346+
PG_RE_THROW();
347+
}
348+
PG_END_TRY();
349+
350+
if (SPI_finish() != SPI_OK_FINISH)
351+
elog(ERROR, "SPI_finish() failed");
352+
353+
return rettup;
354+
}

src/test/modules/plsample/sql/plsample.sql

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,26 @@ AS $$
1313
Example of source with void result.
1414
$$ LANGUAGE plsample;
1515
SELECT plsample_result_void('{foo, bar, hoge}');
16+
17+
CREATE FUNCTION my_trigger_func() RETURNS trigger AS $$
18+
if TD_event == "INSERT"
19+
return TD_NEW
20+
elseif TD_event == "UPDATE"
21+
return TD_NEW
22+
else
23+
return "OK"
24+
end
25+
$$ language plsample;
26+
27+
CREATE TABLE my_table (num integer, description text);
28+
CREATE TRIGGER my_trigger_func BEFORE INSERT OR UPDATE ON my_table
29+
FOR EACH ROW EXECUTE FUNCTION my_trigger_func();
30+
CREATE TRIGGER my_trigger_func2 AFTER INSERT OR UPDATE ON my_table
31+
FOR EACH ROW EXECUTE FUNCTION my_trigger_func(8);
32+
33+
INSERT INTO my_table (num, description)
34+
VALUES (1, 'first');
35+
36+
UPDATE my_table
37+
SET description = 'first, modified once'
38+
WHERE num = 1;

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy