Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * plsample.c
4 : : * Handler for the PL/Sample procedural language
5 : : *
6 : : * Portions Copyright (c) 1996-2024, 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/lsyscache.h"
26 : : #include "utils/syscache.h"
27 : :
1335 michael@paquier.xyz 28 :CBC 1 : PG_MODULE_MAGIC;
29 : :
30 : 2 : 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 : 6 : plsample_call_handler(PG_FUNCTION_ARGS)
40 : : {
41 : 6 : 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 [ + - ]: 6 : PG_TRY();
49 : : {
50 : : /*
51 : : * Determine if called as function or trigger and call appropriate
52 : : * subhandler.
53 : : */
54 [ + + + - ]: 6 : 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 : : */
738 tgl@sss.pgh.pa.us 61 : 4 : retval = PointerGetDatum(plsample_trigger_handler(fcinfo));
62 : : }
1335 michael@paquier.xyz 63 [ - + - - ]: 2 : 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 : : }
73 : : else
74 : : {
75 : : /* Regular function handler */
76 : 2 : retval = plsample_func_handler(fcinfo);
77 : : }
78 : : }
1335 michael@paquier.xyz 79 :UBC 0 : PG_FINALLY();
80 : : {
81 : : }
1335 michael@paquier.xyz 82 [ - + ]:CBC 6 : PG_END_TRY();
83 : :
84 : 6 : return retval;
85 : : }
86 : :
87 : : /*
88 : : * plsample_func_handler
89 : : *
90 : : * Function called by the call handler for function execution.
91 : : */
92 : : static Datum
93 : 2 : plsample_func_handler(PG_FUNCTION_ARGS)
94 : : {
95 : : HeapTuple pl_tuple;
96 : : Datum ret;
97 : : char *source;
98 : : bool isnull;
99 : : FmgrInfo *arg_out_func;
100 : : Form_pg_type type_struct;
101 : : HeapTuple type_tuple;
102 : : Form_pg_proc pl_struct;
103 : 2 : volatile MemoryContext proc_cxt = NULL;
104 : : Oid *argtypes;
105 : : char **argnames;
106 : : char *argmodes;
107 : : char *proname;
108 : : Form_pg_type pg_type_entry;
109 : : Oid result_typioparam;
110 : : Oid prorettype;
111 : : FmgrInfo result_in_func;
112 : : int numargs;
113 : :
114 : : /* Fetch the function's pg_proc entry. */
738 tgl@sss.pgh.pa.us 115 : 2 : pl_tuple = SearchSysCache1(PROCOID,
116 : 2 : ObjectIdGetDatum(fcinfo->flinfo->fn_oid));
1335 michael@paquier.xyz 117 [ - + ]: 2 : if (!HeapTupleIsValid(pl_tuple))
1335 michael@paquier.xyz 118 [ # # ]:UBC 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 : : */
1335 michael@paquier.xyz 125 :CBC 2 : pl_struct = (Form_pg_proc) GETSTRUCT(pl_tuple);
126 : 2 : proname = pstrdup(NameStr(pl_struct->proname));
127 : 2 : ret = SysCacheGetAttr(PROCOID, pl_tuple, Anum_pg_proc_prosrc, &isnull);
128 [ - + ]: 2 : if (isnull)
1335 michael@paquier.xyz 129 [ # # ]:UBC 0 : elog(ERROR, "could not find source text of function \"%s\"",
130 : : proname);
1335 michael@paquier.xyz 131 :CBC 2 : source = DatumGetCString(DirectFunctionCall1(textout, ret));
132 [ + - ]: 2 : 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 : 2 : proc_cxt = AllocSetContextCreate(TopMemoryContext,
141 : : "PL/Sample function",
142 : : ALLOCSET_SMALL_SIZES);
143 : :
144 : 2 : arg_out_func = (FmgrInfo *) palloc0(fcinfo->nargs * sizeof(FmgrInfo));
145 : 2 : 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 [ + + ]: 6 : for (int i = 0; i < numargs; i++)
152 : : {
153 : 4 : Oid argtype = pl_struct->proargtypes.values[i];
154 : : char *value;
155 : :
156 : 4 : type_tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(argtype));
157 [ - + ]: 4 : if (!HeapTupleIsValid(type_tuple))
1335 michael@paquier.xyz 158 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for type %u", argtype);
159 : :
1335 michael@paquier.xyz 160 :CBC 4 : type_struct = (Form_pg_type) GETSTRUCT(type_tuple);
161 : 4 : fmgr_info_cxt(type_struct->typoutput, &(arg_out_func[i]), proc_cxt);
162 : 4 : ReleaseSysCache(type_tuple);
163 : :
164 : 4 : value = OutputFunctionCall(&arg_out_func[i], fcinfo->args[i].value);
165 [ + - ]: 4 : ereport(NOTICE,
166 : : (errmsg("argument: %d; name: %s; value: %s",
167 : : i, argnames[i], value)));
168 : : }
169 : :
170 : : /* Type of the result */
171 : 2 : prorettype = pl_struct->prorettype;
172 : 2 : 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 [ + + ]: 2 : if (prorettype != TEXTOID)
183 : 1 : PG_RETURN_NULL();
184 : :
185 : 1 : type_tuple = SearchSysCache1(TYPEOID,
186 : : ObjectIdGetDatum(prorettype));
187 [ - + ]: 1 : if (!HeapTupleIsValid(type_tuple))
1335 michael@paquier.xyz 188 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for type %u", prorettype);
1335 michael@paquier.xyz 189 :CBC 1 : pg_type_entry = (Form_pg_type) GETSTRUCT(type_tuple);
190 : 1 : result_typioparam = getTypeIOParam(type_tuple);
191 : :
192 : 1 : fmgr_info_cxt(pg_type_entry->typinput, &result_in_func, proc_cxt);
193 : 1 : ReleaseSysCache(type_tuple);
194 : :
195 : 1 : ret = InputFunctionCall(&result_in_func, source, result_typioparam, -1);
196 : 1 : PG_RETURN_DATUM(ret);
197 : : }
198 : :
199 : : /*
200 : : * plsample_trigger_handler
201 : : *
202 : : * Function called by the call handler for trigger execution.
203 : : */
204 : : static HeapTuple
738 tgl@sss.pgh.pa.us 205 : 4 : plsample_trigger_handler(PG_FUNCTION_ARGS)
206 : : {
207 : 4 : 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 [ + - - + ]: 4 : if (!CALLED_AS_TRIGGER(fcinfo))
738 tgl@sss.pgh.pa.us 220 [ # # ]:UBC 0 : elog(ERROR, "not called by trigger manager");
221 : :
222 : : /* Connect to the SPI manager */
738 tgl@sss.pgh.pa.us 223 [ - + ]:CBC 4 : if (SPI_connect() != SPI_OK_CONNECT)
738 tgl@sss.pgh.pa.us 224 [ # # ]:UBC 0 : elog(ERROR, "could not connect to SPI manager");
225 : :
738 tgl@sss.pgh.pa.us 226 :CBC 4 : rc = SPI_register_trigger_data(trigdata);
227 [ - + ]: 4 : 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))
738 tgl@sss.pgh.pa.us 233 [ # # ]:UBC 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 : : */
738 tgl@sss.pgh.pa.us 242 :CBC 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)
738 tgl@sss.pgh.pa.us 246 [ # # ]:UBC 0 : elog(ERROR, "could not find source text of function \"%s\"",
247 : : proname);
738 tgl@sss.pgh.pa.us 248 :CBC 4 : source = DatumGetCString(DirectFunctionCall1(textout, 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 : : {
738 tgl@sss.pgh.pa.us 302 [ # # ]:UBC 0 : ereport(NOTICE, (errmsg("triggered by DELETE")));
303 : 0 : rettup = trigdata->tg_trigtuple;
304 : : }
738 tgl@sss.pgh.pa.us 305 [ + - ]:CBC 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 : : }
738 tgl@sss.pgh.pa.us 310 [ # # ]:UBC 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 : :
738 tgl@sss.pgh.pa.us 318 [ + + ]:CBC 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")));
738 tgl@sss.pgh.pa.us 322 [ # # ]:UBC 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 : :
738 tgl@sss.pgh.pa.us 327 [ + - ]:CBC 4 : if (TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
328 [ + - ]: 4 : ereport(NOTICE, (errmsg("triggered per row")));
738 tgl@sss.pgh.pa.us 329 [ # # ]:UBC 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 : : */
738 tgl@sss.pgh.pa.us 338 [ + + ]:CBC 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 : : }
738 tgl@sss.pgh.pa.us 343 :UBC 0 : PG_CATCH();
344 : : {
345 : : /* Error cleanup code would go here */
346 : 0 : PG_RE_THROW();
347 : : }
738 tgl@sss.pgh.pa.us 348 [ - + ]:CBC 4 : PG_END_TRY();
349 : :
350 [ - + ]: 4 : if (SPI_finish() != SPI_OK_FINISH)
738 tgl@sss.pgh.pa.us 351 [ # # ]:UBC 0 : elog(ERROR, "SPI_finish() failed");
352 : :
738 tgl@sss.pgh.pa.us 353 :CBC 4 : return rettup;
354 : : }
|