Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * pl_handler.c - Handler for the PL/pgSQL
4 : : * 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/pl/plpgsql/src/pl_handler.c
12 : : *
13 : : *-------------------------------------------------------------------------
14 : : */
15 : :
16 : : #include "postgres.h"
17 : :
18 : : #include "access/htup_details.h"
19 : : #include "catalog/pg_proc.h"
20 : : #include "catalog/pg_type.h"
21 : : #include "funcapi.h"
22 : : #include "miscadmin.h"
23 : : #include "plpgsql.h"
24 : : #include "utils/builtins.h"
25 : : #include "utils/guc.h"
26 : : #include "utils/lsyscache.h"
27 : : #include "utils/syscache.h"
28 : : #include "utils/varlena.h"
29 : :
30 : : static bool plpgsql_extra_checks_check_hook(char **newvalue, void **extra, GucSource source);
31 : : static void plpgsql_extra_warnings_assign_hook(const char *newvalue, void *extra);
32 : : static void plpgsql_extra_errors_assign_hook(const char *newvalue, void *extra);
33 : :
34 : 2344 : PG_MODULE_MAGIC_EXT(
35 : : .name = "plpgsql",
36 : : .version = PG_VERSION
37 : : );
38 : :
39 : : /* Custom GUC variable */
40 : : static const struct config_enum_entry variable_conflict_options[] = {
41 : : {"error", PLPGSQL_RESOLVE_ERROR, false},
42 : : {"use_variable", PLPGSQL_RESOLVE_VARIABLE, false},
43 : : {"use_column", PLPGSQL_RESOLVE_COLUMN, false},
44 : : {NULL, 0, false}
45 : : };
46 : :
47 : : int plpgsql_variable_conflict = PLPGSQL_RESOLVE_ERROR;
48 : :
49 : : bool plpgsql_print_strict_params = false;
50 : :
51 : : bool plpgsql_check_asserts = true;
52 : :
53 : : static char *plpgsql_extra_warnings_string = NULL;
54 : : static char *plpgsql_extra_errors_string = NULL;
55 : : int plpgsql_extra_warnings;
56 : : int plpgsql_extra_errors;
57 : :
58 : : /* Hook for plugins */
59 : : PLpgSQL_plugin **plpgsql_plugin_ptr = NULL;
60 : :
61 : :
62 : : static bool
63 : 4732 : plpgsql_extra_checks_check_hook(char **newvalue, void **extra, GucSource source)
64 : : {
65 : : char *rawstring;
66 : : List *elemlist;
67 : : ListCell *l;
68 : 4732 : int extrachecks = 0;
69 : : int *myextra;
70 : :
71 [ + + ]: 4732 : if (pg_strcasecmp(*newvalue, "all") == 0)
72 : 8 : extrachecks = PLPGSQL_XCHECK_ALL;
73 [ + + ]: 4724 : else if (pg_strcasecmp(*newvalue, "none") == 0)
74 : 4696 : extrachecks = PLPGSQL_XCHECK_NONE;
75 : : else
76 : : {
77 : : /* Need a modifiable copy of string */
78 : 28 : rawstring = pstrdup(*newvalue);
79 : :
80 : : /* Parse string into list of identifiers */
81 [ - + ]: 28 : if (!SplitIdentifierString(rawstring, ',', &elemlist))
82 : : {
83 : : /* syntax error in list */
84 : 0 : GUC_check_errdetail("List syntax is invalid.");
85 : 0 : pfree(rawstring);
86 : 0 : list_free(elemlist);
87 : 0 : return false;
88 : : }
89 : :
90 [ + - + + : 56 : foreach(l, elemlist)
+ + ]
91 : : {
92 : 28 : char *tok = (char *) lfirst(l);
93 : :
94 [ + + ]: 28 : if (pg_strcasecmp(tok, "shadowed_variables") == 0)
95 : 12 : extrachecks |= PLPGSQL_XCHECK_SHADOWVAR;
96 [ + + ]: 16 : else if (pg_strcasecmp(tok, "too_many_rows") == 0)
97 : 8 : extrachecks |= PLPGSQL_XCHECK_TOOMANYROWS;
98 [ + - ]: 8 : else if (pg_strcasecmp(tok, "strict_multi_assignment") == 0)
99 : 8 : extrachecks |= PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT;
100 [ # # # # ]: 0 : else if (pg_strcasecmp(tok, "all") == 0 || pg_strcasecmp(tok, "none") == 0)
101 : : {
102 : 0 : GUC_check_errdetail("Key word \"%s\" cannot be combined with other key words.", tok);
103 : 0 : pfree(rawstring);
104 : 0 : list_free(elemlist);
105 : 0 : return false;
106 : : }
107 : : else
108 : : {
109 : 0 : GUC_check_errdetail("Unrecognized key word: \"%s\".", tok);
110 : 0 : pfree(rawstring);
111 : 0 : list_free(elemlist);
112 : 0 : return false;
113 : : }
114 : : }
115 : :
116 : 28 : pfree(rawstring);
117 : 28 : list_free(elemlist);
118 : : }
119 : :
120 : 4732 : myextra = (int *) guc_malloc(LOG, sizeof(int));
121 [ - + ]: 4732 : if (!myextra)
122 : 0 : return false;
123 : 4732 : *myextra = extrachecks;
124 : 4732 : *extra = myextra;
125 : :
126 : 4732 : return true;
127 : : }
128 : :
129 : : static void
130 : 2380 : plpgsql_extra_warnings_assign_hook(const char *newvalue, void *extra)
131 : : {
132 : 2380 : plpgsql_extra_warnings = *((int *) extra);
133 : 2380 : }
134 : :
135 : : static void
136 : 2376 : plpgsql_extra_errors_assign_hook(const char *newvalue, void *extra)
137 : : {
138 : 2376 : plpgsql_extra_errors = *((int *) extra);
139 : 2376 : }
140 : :
141 : :
142 : : /*
143 : : * _PG_init() - library load-time initialization
144 : : *
145 : : * DO NOT make this static nor change its name!
146 : : */
147 : : void
148 : 2344 : _PG_init(void)
149 : : {
150 : : /* Be sure we do initialization only once (should be redundant now) */
151 : : static bool inited = false;
152 : :
153 [ - + ]: 2344 : if (inited)
154 : 0 : return;
155 : :
156 : 2344 : pg_bindtextdomain(TEXTDOMAIN);
157 : :
158 : 2344 : DefineCustomEnumVariable("plpgsql.variable_conflict",
159 : : gettext_noop("Sets handling of conflicts between PL/pgSQL variable names and table column names."),
160 : : NULL,
161 : : &plpgsql_variable_conflict,
162 : : PLPGSQL_RESOLVE_ERROR,
163 : : variable_conflict_options,
164 : : PGC_SUSET, 0,
165 : : NULL, NULL, NULL);
166 : :
167 : 2344 : DefineCustomBoolVariable("plpgsql.print_strict_params",
168 : : gettext_noop("Print information about parameters in the DETAIL part of the error messages generated on INTO ... STRICT failures."),
169 : : NULL,
170 : : &plpgsql_print_strict_params,
171 : : false,
172 : : PGC_USERSET, 0,
173 : : NULL, NULL, NULL);
174 : :
175 : 2344 : DefineCustomBoolVariable("plpgsql.check_asserts",
176 : : gettext_noop("Perform checks given in ASSERT statements."),
177 : : NULL,
178 : : &plpgsql_check_asserts,
179 : : true,
180 : : PGC_USERSET, 0,
181 : : NULL, NULL, NULL);
182 : :
183 : 2344 : DefineCustomStringVariable("plpgsql.extra_warnings",
184 : : gettext_noop("List of programming constructs that should produce a warning."),
185 : : NULL,
186 : : &plpgsql_extra_warnings_string,
187 : : "none",
188 : : PGC_USERSET, GUC_LIST_INPUT,
189 : : plpgsql_extra_checks_check_hook,
190 : : plpgsql_extra_warnings_assign_hook,
191 : : NULL);
192 : :
193 : 2344 : DefineCustomStringVariable("plpgsql.extra_errors",
194 : : gettext_noop("List of programming constructs that should produce an error."),
195 : : NULL,
196 : : &plpgsql_extra_errors_string,
197 : : "none",
198 : : PGC_USERSET, GUC_LIST_INPUT,
199 : : plpgsql_extra_checks_check_hook,
200 : : plpgsql_extra_errors_assign_hook,
201 : : NULL);
202 : :
203 : 2344 : MarkGUCPrefixReserved("plpgsql");
204 : :
205 : 2344 : RegisterXactCallback(plpgsql_xact_cb, NULL);
206 : 2344 : RegisterSubXactCallback(plpgsql_subxact_cb, NULL);
207 : :
208 : : /* Set up a rendezvous point with optional instrumentation plugin */
209 : 2344 : plpgsql_plugin_ptr = (PLpgSQL_plugin **) find_rendezvous_variable("PLpgSQL_plugin");
210 : :
211 : 2344 : inited = true;
212 : : }
213 : :
214 : : /* ----------
215 : : * plpgsql_call_handler
216 : : *
217 : : * The PostgreSQL function manager and trigger manager
218 : : * call this function for execution of PL/pgSQL procedures.
219 : : * ----------
220 : : */
221 : 653 : PG_FUNCTION_INFO_V1(plpgsql_call_handler);
222 : :
223 : : Datum
224 : 56482 : plpgsql_call_handler(PG_FUNCTION_ARGS)
225 : : {
226 : : bool nonatomic;
227 : : PLpgSQL_function *func;
228 : : PLpgSQL_execstate *save_cur_estate;
229 : : ResourceOwner procedure_resowner;
230 : 56482 : volatile Datum retval = (Datum) 0;
231 : : int rc;
232 : :
233 : 125037 : nonatomic = fcinfo->context &&
234 [ + + + + ]: 56595 : IsA(fcinfo->context, CallContext) &&
235 [ + + ]: 113 : !castNode(CallContext, fcinfo->context)->atomic;
236 : :
237 : : /*
238 : : * Connect to SPI manager
239 : : */
240 : 56482 : SPI_connect_ext(nonatomic ? SPI_OPT_NONATOMIC : 0);
241 : :
242 : : /* Find or compile the function */
243 : 56482 : func = plpgsql_compile(fcinfo, false);
244 : :
245 : : /* Must save and restore prior value of cur_estate */
246 : 56473 : save_cur_estate = func->cur_estate;
247 : :
248 : : /* Mark the function as busy, so it can't be deleted from under us */
249 : 56473 : func->cfunc.use_count++;
250 : :
251 : : /*
252 : : * If we'll need a procedure-lifespan resowner to execute any CALL or DO
253 : : * statements, create it now. Since this resowner is not tied to any
254 : : * parent, failing to free it would result in process-lifespan leaks.
255 : : * Therefore, be very wary of adding any code between here and the PG_TRY
256 : : * block.
257 : : */
258 : 56473 : procedure_resowner =
259 [ + + ]: 101 : (nonatomic && func->requires_procedure_resowner) ?
260 [ + + ]: 56574 : ResourceOwnerCreate(NULL, "PL/pgSQL procedure resources") : NULL;
261 : :
262 [ + + ]: 56473 : PG_TRY();
263 : : {
264 : : /*
265 : : * Determine if called as function or trigger and call appropriate
266 : : * subhandler
267 : : */
268 [ + + + + ]: 56473 : if (CALLED_AS_TRIGGER(fcinfo))
269 : 10614 : retval = PointerGetDatum(plpgsql_exec_trigger(func,
270 : : (TriggerData *) fcinfo->context));
271 [ + + + + ]: 45859 : else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
272 : : {
273 : 1138 : plpgsql_exec_event_trigger(func,
274 : 1138 : (EventTriggerData *) fcinfo->context);
275 : : /* there's no return value in this case */
276 : : }
277 : : else
278 : 44313 : retval = plpgsql_exec_function(func, fcinfo,
279 : : NULL, NULL,
280 : : procedure_resowner,
281 : 44721 : !nonatomic);
282 : : }
283 : 566 : PG_FINALLY();
284 : : {
285 : : /* Decrement use-count, restore cur_estate */
286 : 56473 : func->cfunc.use_count--;
287 : 56473 : func->cur_estate = save_cur_estate;
288 : :
289 : : /* Be sure to release the procedure resowner if any */
290 [ + + ]: 56473 : if (procedure_resowner)
291 : : {
292 : 14 : ReleaseAllPlanCacheRefsInOwner(procedure_resowner);
293 : 14 : ResourceOwnerDelete(procedure_resowner);
294 : : }
295 : : }
296 [ + + ]: 56473 : PG_END_TRY();
297 : :
298 : : /*
299 : : * Disconnect from SPI manager
300 : : */
301 [ - + ]: 55907 : if ((rc = SPI_finish()) != SPI_OK_FINISH)
302 [ # # ]: 0 : elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
303 : :
304 : 55907 : return retval;
305 : : }
306 : :
307 : : /* ----------
308 : : * plpgsql_inline_handler
309 : : *
310 : : * Called by PostgreSQL to execute an anonymous code block
311 : : * ----------
312 : : */
313 : 237 : PG_FUNCTION_INFO_V1(plpgsql_inline_handler);
314 : :
315 : : Datum
316 : 885 : plpgsql_inline_handler(PG_FUNCTION_ARGS)
317 : : {
318 : 885 : LOCAL_FCINFO(fake_fcinfo, 0);
319 : 885 : InlineCodeBlock *codeblock = castNode(InlineCodeBlock, DatumGetPointer(PG_GETARG_DATUM(0)));
320 : : PLpgSQL_function *func;
321 : : FmgrInfo flinfo;
322 : : EState *simple_eval_estate;
323 : : ResourceOwner simple_eval_resowner;
324 : : Datum retval;
325 : : int rc;
326 : :
327 : : /*
328 : : * Connect to SPI manager
329 : : */
330 : 885 : SPI_connect_ext(codeblock->atomic ? 0 : SPI_OPT_NONATOMIC);
331 : :
332 : : /* Compile the anonymous code block */
333 : 885 : func = plpgsql_compile_inline(codeblock->source_text);
334 : :
335 : : /* Mark the function as busy, just pro forma */
336 : 856 : func->cfunc.use_count++;
337 : :
338 : : /*
339 : : * Set up a fake fcinfo with just enough info to satisfy
340 : : * plpgsql_exec_function(). In particular note that this sets things up
341 : : * with no arguments passed.
342 : : */
343 [ + - + - : 4280 : MemSet(fake_fcinfo, 0, SizeForFunctionCallInfo(0));
+ - + - +
+ ]
344 [ + - + - : 5992 : MemSet(&flinfo, 0, sizeof(flinfo));
+ - + - +
+ ]
345 : 856 : fake_fcinfo->flinfo = &flinfo;
346 : 856 : flinfo.fn_oid = InvalidOid;
347 : 856 : flinfo.fn_mcxt = CurrentMemoryContext;
348 : :
349 : : /*
350 : : * Create a private EState and resowner for simple-expression execution.
351 : : * Notice that these are NOT tied to transaction-level resources; they
352 : : * must survive any COMMIT/ROLLBACK the DO block executes, since we will
353 : : * unconditionally try to clean them up below. (Hence, be wary of adding
354 : : * anything that could fail between here and the PG_TRY block.) See the
355 : : * comments for shared_simple_eval_estate.
356 : : *
357 : : * Because this resowner isn't tied to the calling transaction, we can
358 : : * also use it as the "procedure" resowner for any CALL statements. That
359 : : * helps reduce the opportunities for failure here.
360 : : */
361 : 856 : simple_eval_estate = CreateExecutorState();
362 : : simple_eval_resowner =
363 : 856 : ResourceOwnerCreate(NULL, "PL/pgSQL DO block simple expressions");
364 : :
365 : : /* And run the function */
366 [ + + ]: 856 : PG_TRY();
367 : : {
368 : 664 : retval = plpgsql_exec_function(func, fake_fcinfo,
369 : : simple_eval_estate,
370 : : simple_eval_resowner,
371 : : simple_eval_resowner, /* see above */
372 : 856 : codeblock->atomic);
373 : : }
374 : 192 : PG_CATCH();
375 : : {
376 : : /*
377 : : * We need to clean up what would otherwise be long-lived resources
378 : : * accumulated by the failed DO block, principally cached plans for
379 : : * statements (which can be flushed by plpgsql_free_function_memory),
380 : : * execution trees for simple expressions, which are in the private
381 : : * EState, and cached-plan refcounts held by the private resowner.
382 : : *
383 : : * Before releasing the private EState, we must clean up any
384 : : * simple_econtext_stack entries pointing into it, which we can do by
385 : : * invoking the subxact callback. (It will be called again later if
386 : : * some outer control level does a subtransaction abort, but no harm
387 : : * is done.) We cheat a bit knowing that plpgsql_subxact_cb does not
388 : : * pay attention to its parentSubid argument.
389 : : */
390 : 192 : plpgsql_subxact_cb(SUBXACT_EVENT_ABORT_SUB,
391 : : GetCurrentSubTransactionId(),
392 : : 0, NULL);
393 : :
394 : : /* Clean up the private EState and resowner */
395 : 192 : FreeExecutorState(simple_eval_estate);
396 : 192 : ReleaseAllPlanCacheRefsInOwner(simple_eval_resowner);
397 : 192 : ResourceOwnerDelete(simple_eval_resowner);
398 : :
399 : : /* Function should now have no remaining use-counts ... */
400 : 192 : func->cfunc.use_count--;
401 : : Assert(func->cfunc.use_count == 0);
402 : :
403 : : /* ... so we can free subsidiary storage */
404 : 192 : plpgsql_free_function_memory(func);
405 : :
406 : : /* And propagate the error */
407 : 192 : PG_RE_THROW();
408 : : }
409 [ - + ]: 664 : PG_END_TRY();
410 : :
411 : : /* Clean up the private EState and resowner */
412 : 664 : FreeExecutorState(simple_eval_estate);
413 : 664 : ReleaseAllPlanCacheRefsInOwner(simple_eval_resowner);
414 : 664 : ResourceOwnerDelete(simple_eval_resowner);
415 : :
416 : : /* Function should now have no remaining use-counts ... */
417 : 664 : func->cfunc.use_count--;
418 : : Assert(func->cfunc.use_count == 0);
419 : :
420 : : /* ... so we can free subsidiary storage */
421 : 664 : plpgsql_free_function_memory(func);
422 : :
423 : : /*
424 : : * Disconnect from SPI manager
425 : : */
426 [ - + ]: 664 : if ((rc = SPI_finish()) != SPI_OK_FINISH)
427 [ # # ]: 0 : elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
428 : :
429 : 664 : return retval;
430 : : }
431 : :
432 : : /* ----------
433 : : * plpgsql_validator
434 : : *
435 : : * This function attempts to validate a PL/pgSQL function at
436 : : * CREATE FUNCTION time.
437 : : * ----------
438 : : */
439 : 461 : PG_FUNCTION_INFO_V1(plpgsql_validator);
440 : :
441 : : Datum
442 : 2974 : plpgsql_validator(PG_FUNCTION_ARGS)
443 : : {
444 : 2974 : Oid funcoid = PG_GETARG_OID(0);
445 : : HeapTuple tuple;
446 : : Form_pg_proc proc;
447 : : char functyptype;
448 : : int numargs;
449 : : Oid *argtypes;
450 : : char **argnames;
451 : : char *argmodes;
452 : 2974 : bool is_dml_trigger = false;
453 : 2974 : bool is_event_trigger = false;
454 : : int i;
455 : :
456 [ - + ]: 2974 : if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
457 : 0 : PG_RETURN_VOID();
458 : :
459 : : /* Get the new function's pg_proc entry */
460 : 2974 : tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
461 [ - + ]: 2974 : if (!HeapTupleIsValid(tuple))
462 [ # # ]: 0 : elog(ERROR, "cache lookup failed for function %u", funcoid);
463 : 2974 : proc = (Form_pg_proc) GETSTRUCT(tuple);
464 : :
465 : 2974 : functyptype = get_typtype(proc->prorettype);
466 : :
467 : : /* Disallow pseudotype result */
468 : : /* except for TRIGGER, EVTTRIGGER, RECORD, VOID, or polymorphic */
469 [ + + ]: 2974 : if (functyptype == TYPTYPE_PSEUDO)
470 : : {
471 [ + + ]: 1780 : if (proc->prorettype == TRIGGEROID)
472 : 909 : is_dml_trigger = true;
473 [ + + ]: 871 : else if (proc->prorettype == EVENT_TRIGGEROID)
474 : 92 : is_event_trigger = true;
475 [ + + ]: 779 : else if (proc->prorettype != RECORDOID &&
476 [ + + ]: 660 : proc->prorettype != VOIDOID &&
477 [ + + + + : 50 : !IsPolymorphicType(proc->prorettype))
+ - + - +
- + + + -
+ + + - -
+ - - ]
478 [ # # ]: 0 : ereport(ERROR,
479 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
480 : : errmsg("PL/pgSQL functions cannot return type %s",
481 : : format_type_be(proc->prorettype))));
482 : : }
483 : :
484 : : /* Disallow pseudotypes in arguments (either IN or OUT) */
485 : : /* except for RECORD and polymorphic */
486 : 2974 : numargs = get_func_arg_info(tuple,
487 : : &argtypes, &argnames, &argmodes);
488 [ + + ]: 5019 : for (i = 0; i < numargs; i++)
489 : : {
490 [ + + ]: 2045 : if (get_typtype(argtypes[i]) == TYPTYPE_PSEUDO)
491 : : {
492 [ + + ]: 370 : if (argtypes[i] != RECORDOID &&
493 [ + + + + : 356 : !IsPolymorphicType(argtypes[i]))
+ + + + +
+ + - + +
+ + + - -
+ - - ]
494 [ # # ]: 0 : ereport(ERROR,
495 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
496 : : errmsg("PL/pgSQL functions cannot accept type %s",
497 : : format_type_be(argtypes[i]))));
498 : : }
499 : : }
500 : :
501 : : /* Postpone body checks if !check_function_bodies */
502 [ + + ]: 2974 : if (check_function_bodies)
503 : : {
504 : 2822 : LOCAL_FCINFO(fake_fcinfo, 0);
505 : : FmgrInfo flinfo;
506 : : int rc;
507 : : TriggerData trigdata;
508 : : EventTriggerData etrigdata;
509 : :
510 : : /*
511 : : * Connect to SPI manager (is this needed for compilation?)
512 : : */
513 : 2822 : SPI_connect();
514 : :
515 : : /*
516 : : * Set up a fake fcinfo with just enough info to satisfy
517 : : * plpgsql_compile().
518 : : */
519 [ + - + - : 14110 : MemSet(fake_fcinfo, 0, SizeForFunctionCallInfo(0));
+ - + - +
+ ]
520 [ + - + - : 19754 : MemSet(&flinfo, 0, sizeof(flinfo));
+ - + - +
+ ]
521 : 2822 : fake_fcinfo->flinfo = &flinfo;
522 : 2822 : flinfo.fn_oid = funcoid;
523 : 2822 : flinfo.fn_mcxt = CurrentMemoryContext;
524 [ + + ]: 2822 : if (is_dml_trigger)
525 : : {
526 [ + - + - : 9350 : MemSet(&trigdata, 0, sizeof(trigdata));
+ - + - +
+ ]
527 : 850 : trigdata.type = T_TriggerData;
528 : 850 : fake_fcinfo->context = (Node *) &trigdata;
529 : : }
530 [ + + ]: 1972 : else if (is_event_trigger)
531 : : {
532 [ + - + - : 410 : MemSet(&etrigdata, 0, sizeof(etrigdata));
+ - + - +
+ ]
533 : 82 : etrigdata.type = T_EventTriggerData;
534 : 82 : fake_fcinfo->context = (Node *) &etrigdata;
535 : : }
536 : :
537 : : /* Test-compile the function */
538 : 2822 : plpgsql_compile(fake_fcinfo, true);
539 : :
540 : : /*
541 : : * Disconnect from SPI manager
542 : : */
543 [ - + ]: 2753 : if ((rc = SPI_finish()) != SPI_OK_FINISH)
544 [ # # ]: 0 : elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
545 : : }
546 : :
547 : 2905 : ReleaseSysCache(tuple);
548 : :
549 : 2905 : PG_RETURN_VOID();
550 : : }
|