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/fmgrprotos.h"
25 : #include "utils/lsyscache.h"
26 : #include "utils/syscache.h"
27 :
28 0 : PG_MODULE_MAGIC;
29 :
30 0 : PG_FUNCTION_INFO_V1(plsample_call_handler);
31 :
32 : static Datum plsample_func_handler(PG_FUNCTION_ARGS);
33 : static HeapTuple plsample_trigger_handler(PG_FUNCTION_ARGS);
34 :
35 : /*
36 : * Handle function, procedure, and trigger calls.
37 : */
38 : Datum
39 0 : plsample_call_handler(PG_FUNCTION_ARGS)
40 : {
41 0 : Datum retval = (Datum) 0;
42 :
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 : */
48 0 : PG_TRY();
49 : {
50 : /*
51 : * Determine if called as function or trigger and call appropriate
52 : * subhandler.
53 : */
54 0 : if (CALLED_AS_TRIGGER(fcinfo))
55 : {
56 : /*
57 : * This function has been called as a trigger function, where
58 : * (TriggerData *) fcinfo->context includes the information of the
59 : * context.
60 : */
61 0 : retval = PointerGetDatum(plsample_trigger_handler(fcinfo));
62 0 : }
63 0 : else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
64 : {
65 : /*
66 : * This function is called as an event trigger function, where
67 : * (EventTriggerData *) fcinfo->context includes the information
68 : * of the context.
69 : *
70 : * TODO: provide an example handler.
71 : */
72 0 : }
73 : else
74 : {
75 : /* Regular function handler */
76 0 : retval = plsample_func_handler(fcinfo);
77 : }
78 : }
79 0 : PG_FINALLY();
80 : {
81 : }
82 0 : PG_END_TRY();
83 :
84 0 : return retval;
85 0 : }
86 :
87 : /*
88 : * plsample_func_handler
89 : *
90 : * Function called by the call handler for function execution.
91 : */
92 : static Datum
93 0 : plsample_func_handler(PG_FUNCTION_ARGS)
94 : {
95 0 : HeapTuple pl_tuple;
96 0 : Datum ret;
97 0 : char *source;
98 0 : bool isnull;
99 0 : FmgrInfo *arg_out_func;
100 0 : Form_pg_type type_struct;
101 0 : HeapTuple type_tuple;
102 0 : Form_pg_proc pl_struct;
103 0 : volatile MemoryContext proc_cxt = NULL;
104 0 : Oid *argtypes;
105 0 : char **argnames;
106 0 : char *argmodes;
107 0 : char *proname;
108 0 : Form_pg_type pg_type_entry;
109 0 : Oid result_typioparam;
110 0 : Oid prorettype;
111 0 : FmgrInfo result_in_func;
112 0 : int numargs;
113 :
114 : /* Fetch the function's pg_proc entry. */
115 0 : pl_tuple = SearchSysCache1(PROCOID,
116 0 : ObjectIdGetDatum(fcinfo->flinfo->fn_oid));
117 0 : if (!HeapTupleIsValid(pl_tuple))
118 0 : elog(ERROR, "cache lookup failed for function %u",
119 : fcinfo->flinfo->fn_oid);
120 :
121 : /*
122 : * Extract and print the source text of the function. This can be used as
123 : * a base for the function validation and execution.
124 : */
125 0 : pl_struct = (Form_pg_proc) GETSTRUCT(pl_tuple);
126 0 : proname = pstrdup(NameStr(pl_struct->proname));
127 0 : ret = SysCacheGetAttr(PROCOID, pl_tuple, Anum_pg_proc_prosrc, &isnull);
128 0 : if (isnull)
129 0 : elog(ERROR, "could not find source text of function \"%s\"",
130 : proname);
131 0 : source = DatumGetCString(DirectFunctionCall1(textout, ret));
132 0 : ereport(NOTICE,
133 : (errmsg("source text of function \"%s\": %s",
134 : proname, source)));
135 :
136 : /*
137 : * Allocate a context that will hold all the Postgres data for the
138 : * procedure.
139 : */
140 0 : proc_cxt = AllocSetContextCreate(TopMemoryContext,
141 : "PL/Sample function",
142 : ALLOCSET_SMALL_SIZES);
143 :
144 0 : arg_out_func = (FmgrInfo *) palloc0(fcinfo->nargs * sizeof(FmgrInfo));
145 0 : numargs = get_func_arg_info(pl_tuple, &argtypes, &argnames, &argmodes);
146 :
147 : /*
148 : * Iterate through all of the function arguments, printing each input
149 : * value.
150 : */
151 0 : for (int i = 0; i < numargs; i++)
152 : {
153 0 : Oid argtype = pl_struct->proargtypes.values[i];
154 0 : char *value;
155 :
156 0 : type_tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(argtype));
157 0 : if (!HeapTupleIsValid(type_tuple))
158 0 : elog(ERROR, "cache lookup failed for type %u", argtype);
159 :
160 0 : type_struct = (Form_pg_type) GETSTRUCT(type_tuple);
161 0 : fmgr_info_cxt(type_struct->typoutput, &(arg_out_func[i]), proc_cxt);
162 0 : ReleaseSysCache(type_tuple);
163 :
164 0 : value = OutputFunctionCall(&arg_out_func[i], fcinfo->args[i].value);
165 0 : ereport(NOTICE,
166 : (errmsg("argument: %d; name: %s; value: %s",
167 : i, argnames[i], value)));
168 0 : }
169 :
170 : /* Type of the result */
171 0 : prorettype = pl_struct->prorettype;
172 0 : ReleaseSysCache(pl_tuple);
173 :
174 : /*
175 : * Get the required information for input conversion of the return value.
176 : *
177 : * If the function uses VOID as result, it is better to return NULL.
178 : * Anyway, let's be honest. This is just a template, so there is not much
179 : * we can do here. This returns NULL except if the result type is text,
180 : * where the result is the source text of the function.
181 : */
182 0 : if (prorettype != TEXTOID)
183 0 : PG_RETURN_NULL();
184 :
185 0 : type_tuple = SearchSysCache1(TYPEOID,
186 0 : ObjectIdGetDatum(prorettype));
187 0 : if (!HeapTupleIsValid(type_tuple))
188 0 : elog(ERROR, "cache lookup failed for type %u", prorettype);
189 0 : pg_type_entry = (Form_pg_type) GETSTRUCT(type_tuple);
190 0 : result_typioparam = getTypeIOParam(type_tuple);
191 :
192 0 : fmgr_info_cxt(pg_type_entry->typinput, &result_in_func, proc_cxt);
193 0 : ReleaseSysCache(type_tuple);
194 :
195 0 : ret = InputFunctionCall(&result_in_func, source, result_typioparam, -1);
196 0 : PG_RETURN_DATUM(ret);
197 0 : }
198 :
199 : /*
200 : * plsample_trigger_handler
201 : *
202 : * Function called by the call handler for trigger execution.
203 : */
204 : static HeapTuple
205 0 : plsample_trigger_handler(PG_FUNCTION_ARGS)
206 : {
207 0 : TriggerData *trigdata = (TriggerData *) fcinfo->context;
208 0 : char *string;
209 0 : volatile HeapTuple rettup;
210 0 : HeapTuple pl_tuple;
211 0 : Datum ret;
212 0 : char *source;
213 0 : bool isnull;
214 0 : Form_pg_proc pl_struct;
215 0 : char *proname;
216 0 : int rc PG_USED_FOR_ASSERTS_ONLY;
217 :
218 : /* Make sure this is being called from a trigger. */
219 0 : if (!CALLED_AS_TRIGGER(fcinfo))
220 0 : elog(ERROR, "not called by trigger manager");
221 :
222 : /* Connect to the SPI manager */
223 0 : SPI_connect();
224 :
225 0 : rc = SPI_register_trigger_data(trigdata);
226 0 : Assert(rc >= 0);
227 :
228 : /* Fetch the function's pg_proc entry. */
229 0 : pl_tuple = SearchSysCache1(PROCOID,
230 0 : ObjectIdGetDatum(fcinfo->flinfo->fn_oid));
231 0 : if (!HeapTupleIsValid(pl_tuple))
232 0 : elog(ERROR, "cache lookup failed for function %u",
233 : fcinfo->flinfo->fn_oid);
234 :
235 : /*
236 : * Code Retrieval
237 : *
238 : * Extract and print the source text of the function. This can be used as
239 : * a base for the function validation and execution.
240 : */
241 0 : pl_struct = (Form_pg_proc) GETSTRUCT(pl_tuple);
242 0 : proname = pstrdup(NameStr(pl_struct->proname));
243 0 : ret = SysCacheGetAttr(PROCOID, pl_tuple, Anum_pg_proc_prosrc, &isnull);
244 0 : if (isnull)
245 0 : elog(ERROR, "could not find source text of function \"%s\"",
246 : proname);
247 0 : source = DatumGetCString(DirectFunctionCall1(textout, ret));
248 0 : ereport(NOTICE,
249 : (errmsg("source text of function \"%s\": %s",
250 : proname, source)));
251 :
252 : /*
253 : * We're done with the pg_proc tuple, so release it. (Note that the
254 : * "proname" and "source" strings are now standalone copies.)
255 : */
256 0 : ReleaseSysCache(pl_tuple);
257 :
258 : /*
259 : * Code Augmentation
260 : *
261 : * The source text may be augmented here, such as by wrapping it as the
262 : * body of a function in the target language, prefixing a parameter list
263 : * with names like TD_name, TD_relid, TD_table_name, TD_table_schema,
264 : * TD_event, TD_when, TD_level, TD_NEW, TD_OLD, and args, using whatever
265 : * types in the target language are convenient. The augmented text can be
266 : * cached in a longer-lived memory context, or, if the target language
267 : * uses a compilation step, that can be done here, caching the result of
268 : * the compilation.
269 : */
270 :
271 : /*
272 : * Code Execution
273 : *
274 : * Here the function (the possibly-augmented source text, or the result of
275 : * compilation if the target language uses such a step) should be
276 : * executed, after binding values from the TriggerData struct to the
277 : * appropriate parameters.
278 : *
279 : * In this example we just print a lot of info via ereport.
280 : */
281 :
282 0 : PG_TRY();
283 : {
284 0 : ereport(NOTICE,
285 : (errmsg("trigger name: %s", trigdata->tg_trigger->tgname)));
286 0 : string = SPI_getrelname(trigdata->tg_relation);
287 0 : ereport(NOTICE, (errmsg("trigger relation: %s", string)));
288 :
289 0 : string = SPI_getnspname(trigdata->tg_relation);
290 0 : ereport(NOTICE, (errmsg("trigger relation schema: %s", string)));
291 :
292 : /* Example handling of different trigger aspects. */
293 :
294 0 : if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
295 : {
296 0 : ereport(NOTICE, (errmsg("triggered by INSERT")));
297 0 : rettup = trigdata->tg_trigtuple;
298 0 : }
299 0 : else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
300 : {
301 0 : ereport(NOTICE, (errmsg("triggered by DELETE")));
302 0 : rettup = trigdata->tg_trigtuple;
303 0 : }
304 0 : else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
305 : {
306 0 : ereport(NOTICE, (errmsg("triggered by UPDATE")));
307 0 : rettup = trigdata->tg_trigtuple;
308 0 : }
309 0 : else if (TRIGGER_FIRED_BY_TRUNCATE(trigdata->tg_event))
310 : {
311 0 : ereport(NOTICE, (errmsg("triggered by TRUNCATE")));
312 0 : rettup = trigdata->tg_trigtuple;
313 0 : }
314 : else
315 0 : elog(ERROR, "unrecognized event: %u", trigdata->tg_event);
316 :
317 0 : if (TRIGGER_FIRED_BEFORE(trigdata->tg_event))
318 0 : ereport(NOTICE, (errmsg("triggered BEFORE")));
319 0 : else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
320 0 : ereport(NOTICE, (errmsg("triggered AFTER")));
321 0 : else if (TRIGGER_FIRED_INSTEAD(trigdata->tg_event))
322 0 : ereport(NOTICE, (errmsg("triggered INSTEAD OF")));
323 : else
324 0 : elog(ERROR, "unrecognized when: %u", trigdata->tg_event);
325 :
326 0 : if (TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
327 0 : ereport(NOTICE, (errmsg("triggered per row")));
328 0 : else if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
329 0 : ereport(NOTICE, (errmsg("triggered per statement")));
330 : else
331 0 : elog(ERROR, "unrecognized level: %u", trigdata->tg_event);
332 :
333 : /*
334 : * Iterate through all of the trigger arguments, printing each input
335 : * value.
336 : */
337 0 : for (int i = 0; i < trigdata->tg_trigger->tgnargs; i++)
338 0 : ereport(NOTICE,
339 : (errmsg("trigger arg[%i]: %s", i,
340 : trigdata->tg_trigger->tgargs[i])));
341 : }
342 0 : PG_CATCH();
343 : {
344 : /* Error cleanup code would go here */
345 0 : PG_RE_THROW();
346 : }
347 0 : PG_END_TRY();
348 :
349 0 : if (SPI_finish() != SPI_OK_FINISH)
350 0 : elog(ERROR, "SPI_finish() failed");
351 :
352 0 : return rettup;
353 0 : }
|