Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * plsample.c
4 : * Handler for the PL/Sample procedural language
5 : *
6 : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : * Portions Copyright (c) 1994, Regents of the University of California
8 : *
9 : *
10 : * IDENTIFICATION
11 : * src/test/modules/plsample/plsample.c
12 : *
13 : *-------------------------------------------------------------------------
14 : */
15 :
16 : #include "postgres.h"
17 :
18 : #include "catalog/pg_proc.h"
19 : #include "catalog/pg_type.h"
20 : #include "commands/event_trigger.h"
21 : #include "commands/trigger.h"
22 : #include "executor/spi.h"
23 : #include "funcapi.h"
24 : #include "utils/builtins.h"
25 : #include "utils/fmgrprotos.h"
26 : #include "utils/lsyscache.h"
27 : #include "utils/syscache.h"
28 :
29 1 : PG_MODULE_MAGIC;
30 :
31 2 : PG_FUNCTION_INFO_V1(plsample_call_handler);
32 :
33 : static Datum plsample_func_handler(PG_FUNCTION_ARGS);
34 : static HeapTuple plsample_trigger_handler(PG_FUNCTION_ARGS);
35 :
36 : /*
37 : * Handle function, procedure, and trigger calls.
38 : */
39 : Datum
40 6 : plsample_call_handler(PG_FUNCTION_ARGS)
41 : {
42 6 : Datum retval = (Datum) 0;
43 :
44 : /*
45 : * Many languages will require cleanup that happens even in the event of
46 : * an error. That can happen in the PG_FINALLY block. If none is needed,
47 : * this PG_TRY construct can be omitted.
48 : */
49 6 : PG_TRY();
50 : {
51 : /*
52 : * Determine if called as function or trigger and call appropriate
53 : * subhandler.
54 : */
55 6 : if (CALLED_AS_TRIGGER(fcinfo))
56 : {
57 : /*
58 : * This function has been called as a trigger function, where
59 : * (TriggerData *) fcinfo->context includes the information of the
60 : * context.
61 : */
62 4 : retval = PointerGetDatum(plsample_trigger_handler(fcinfo));
63 : }
64 2 : else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
65 : {
66 : /*
67 : * This function is called as an event trigger function, where
68 : * (EventTriggerData *) fcinfo->context includes the information
69 : * of the context.
70 : *
71 : * TODO: provide an example handler.
72 : */
73 : }
74 : else
75 : {
76 : /* Regular function handler */
77 2 : retval = plsample_func_handler(fcinfo);
78 : }
79 : }
80 0 : PG_FINALLY();
81 : {
82 : }
83 6 : PG_END_TRY();
84 :
85 6 : return retval;
86 : }
87 :
88 : /*
89 : * plsample_func_handler
90 : *
91 : * Function called by the call handler for function execution.
92 : */
93 : static Datum
94 2 : plsample_func_handler(PG_FUNCTION_ARGS)
95 : {
96 : HeapTuple pl_tuple;
97 : Datum ret;
98 : char *source;
99 : bool isnull;
100 : FmgrInfo *arg_out_func;
101 : Form_pg_type type_struct;
102 : HeapTuple type_tuple;
103 : Form_pg_proc pl_struct;
104 2 : volatile MemoryContext proc_cxt = NULL;
105 : Oid *argtypes;
106 : char **argnames;
107 : char *argmodes;
108 : char *proname;
109 : Form_pg_type pg_type_entry;
110 : Oid result_typioparam;
111 : Oid prorettype;
112 : FmgrInfo result_in_func;
113 : int numargs;
114 :
115 : /* Fetch the function's pg_proc entry. */
116 2 : pl_tuple = SearchSysCache1(PROCOID,
117 2 : ObjectIdGetDatum(fcinfo->flinfo->fn_oid));
118 2 : if (!HeapTupleIsValid(pl_tuple))
119 0 : elog(ERROR, "cache lookup failed for function %u",
120 : fcinfo->flinfo->fn_oid);
121 :
122 : /*
123 : * Extract and print the source text of the function. This can be used as
124 : * a base for the function validation and execution.
125 : */
126 2 : pl_struct = (Form_pg_proc) GETSTRUCT(pl_tuple);
127 2 : proname = pstrdup(NameStr(pl_struct->proname));
128 2 : ret = SysCacheGetAttr(PROCOID, pl_tuple, Anum_pg_proc_prosrc, &isnull);
129 2 : if (isnull)
130 0 : elog(ERROR, "could not find source text of function \"%s\"",
131 : proname);
132 2 : source = TextDatumGetCString(ret);
133 2 : ereport(NOTICE,
134 : (errmsg("source text of function \"%s\": %s",
135 : proname, source)));
136 :
137 : /*
138 : * Allocate a context that will hold all the Postgres data for the
139 : * procedure.
140 : */
141 2 : proc_cxt = AllocSetContextCreate(TopMemoryContext,
142 : "PL/Sample function",
143 : ALLOCSET_SMALL_SIZES);
144 :
145 2 : arg_out_func = (FmgrInfo *) palloc0(fcinfo->nargs * sizeof(FmgrInfo));
146 2 : numargs = get_func_arg_info(pl_tuple, &argtypes, &argnames, &argmodes);
147 :
148 : /*
149 : * Iterate through all of the function arguments, printing each input
150 : * value.
151 : */
152 6 : for (int i = 0; i < numargs; i++)
153 : {
154 4 : Oid argtype = pl_struct->proargtypes.values[i];
155 : char *value;
156 :
157 4 : type_tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(argtype));
158 4 : if (!HeapTupleIsValid(type_tuple))
159 0 : elog(ERROR, "cache lookup failed for type %u", argtype);
160 :
161 4 : type_struct = (Form_pg_type) GETSTRUCT(type_tuple);
162 4 : fmgr_info_cxt(type_struct->typoutput, &(arg_out_func[i]), proc_cxt);
163 4 : ReleaseSysCache(type_tuple);
164 :
165 4 : value = OutputFunctionCall(&arg_out_func[i], fcinfo->args[i].value);
166 4 : ereport(NOTICE,
167 : (errmsg("argument: %d; name: %s; value: %s",
168 : i, argnames[i], value)));
169 : }
170 :
171 : /* Type of the result */
172 2 : prorettype = pl_struct->prorettype;
173 2 : ReleaseSysCache(pl_tuple);
174 :
175 : /*
176 : * Get the required information for input conversion of the return value.
177 : *
178 : * If the function uses VOID as result, it is better to return NULL.
179 : * Anyway, let's be honest. This is just a template, so there is not much
180 : * we can do here. This returns NULL except if the result type is text,
181 : * where the result is the source text of the function.
182 : */
183 2 : if (prorettype != TEXTOID)
184 1 : PG_RETURN_NULL();
185 :
186 1 : type_tuple = SearchSysCache1(TYPEOID,
187 : ObjectIdGetDatum(prorettype));
188 1 : if (!HeapTupleIsValid(type_tuple))
189 0 : elog(ERROR, "cache lookup failed for type %u", prorettype);
190 1 : pg_type_entry = (Form_pg_type) GETSTRUCT(type_tuple);
191 1 : result_typioparam = getTypeIOParam(type_tuple);
192 :
193 1 : fmgr_info_cxt(pg_type_entry->typinput, &result_in_func, proc_cxt);
194 1 : ReleaseSysCache(type_tuple);
195 :
196 1 : ret = InputFunctionCall(&result_in_func, source, result_typioparam, -1);
197 1 : PG_RETURN_DATUM(ret);
198 : }
199 :
200 : /*
201 : * plsample_trigger_handler
202 : *
203 : * Function called by the call handler for trigger execution.
204 : */
205 : static HeapTuple
206 4 : plsample_trigger_handler(PG_FUNCTION_ARGS)
207 : {
208 4 : TriggerData *trigdata = (TriggerData *) fcinfo->context;
209 : char *string;
210 : volatile HeapTuple rettup;
211 : HeapTuple pl_tuple;
212 : Datum ret;
213 : char *source;
214 : bool isnull;
215 : Form_pg_proc pl_struct;
216 : char *proname;
217 : int rc PG_USED_FOR_ASSERTS_ONLY;
218 :
219 : /* Make sure this is being called from a trigger. */
220 4 : if (!CALLED_AS_TRIGGER(fcinfo))
221 0 : elog(ERROR, "not called by trigger manager");
222 :
223 : /* Connect to the SPI manager */
224 4 : SPI_connect();
225 :
226 4 : rc = SPI_register_trigger_data(trigdata);
227 : Assert(rc >= 0);
228 :
229 : /* Fetch the function's pg_proc entry. */
230 4 : pl_tuple = SearchSysCache1(PROCOID,
231 4 : ObjectIdGetDatum(fcinfo->flinfo->fn_oid));
232 4 : if (!HeapTupleIsValid(pl_tuple))
233 0 : 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 4 : pl_struct = (Form_pg_proc) GETSTRUCT(pl_tuple);
243 4 : proname = pstrdup(NameStr(pl_struct->proname));
244 4 : ret = SysCacheGetAttr(PROCOID, pl_tuple, Anum_pg_proc_prosrc, &isnull);
245 4 : if (isnull)
246 0 : elog(ERROR, "could not find source text of function \"%s\"",
247 : proname);
248 4 : source = TextDatumGetCString(ret);
249 4 : 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 4 : 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 4 : PG_TRY();
284 : {
285 4 : ereport(NOTICE,
286 : (errmsg("trigger name: %s", trigdata->tg_trigger->tgname)));
287 4 : string = SPI_getrelname(trigdata->tg_relation);
288 4 : ereport(NOTICE, (errmsg("trigger relation: %s", string)));
289 :
290 4 : string = SPI_getnspname(trigdata->tg_relation);
291 4 : ereport(NOTICE, (errmsg("trigger relation schema: %s", string)));
292 :
293 : /* Example handling of different trigger aspects. */
294 :
295 4 : if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
296 : {
297 2 : ereport(NOTICE, (errmsg("triggered by INSERT")));
298 2 : rettup = trigdata->tg_trigtuple;
299 : }
300 2 : else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
301 : {
302 0 : ereport(NOTICE, (errmsg("triggered by DELETE")));
303 0 : rettup = trigdata->tg_trigtuple;
304 : }
305 2 : else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
306 : {
307 2 : ereport(NOTICE, (errmsg("triggered by UPDATE")));
308 2 : rettup = trigdata->tg_trigtuple;
309 : }
310 0 : else if (TRIGGER_FIRED_BY_TRUNCATE(trigdata->tg_event))
311 : {
312 0 : ereport(NOTICE, (errmsg("triggered by TRUNCATE")));
313 0 : rettup = trigdata->tg_trigtuple;
314 : }
315 : else
316 0 : elog(ERROR, "unrecognized event: %u", trigdata->tg_event);
317 :
318 4 : if (TRIGGER_FIRED_BEFORE(trigdata->tg_event))
319 2 : ereport(NOTICE, (errmsg("triggered BEFORE")));
320 2 : else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
321 2 : ereport(NOTICE, (errmsg("triggered AFTER")));
322 0 : else if (TRIGGER_FIRED_INSTEAD(trigdata->tg_event))
323 0 : ereport(NOTICE, (errmsg("triggered INSTEAD OF")));
324 : else
325 0 : elog(ERROR, "unrecognized when: %u", trigdata->tg_event);
326 :
327 4 : if (TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
328 4 : ereport(NOTICE, (errmsg("triggered per row")));
329 0 : else if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
330 0 : ereport(NOTICE, (errmsg("triggered per statement")));
331 : else
332 0 : 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 6 : for (int i = 0; i < trigdata->tg_trigger->tgnargs; i++)
339 2 : ereport(NOTICE,
340 : (errmsg("trigger arg[%i]: %s", i,
341 : trigdata->tg_trigger->tgargs[i])));
342 : }
343 0 : PG_CATCH();
344 : {
345 : /* Error cleanup code would go here */
346 0 : PG_RE_THROW();
347 : }
348 4 : PG_END_TRY();
349 :
350 4 : if (SPI_finish() != SPI_OK_FINISH)
351 0 : elog(ERROR, "SPI_finish() failed");
352 :
353 4 : return rettup;
354 : }
|