LCOV - code coverage report
Current view: top level - src/pl/plpgsql/src - pl_handler.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 87.1 % 171 149
Test Date: 2026-05-12 00:16:36 Functions: 100.0 % 11 11
Legend: Lines:     hit not hit

            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         2334 : 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         4712 : plpgsql_extra_checks_check_hook(char **newvalue, void **extra, GucSource source)
      64              : {
      65              :     char       *rawstring;
      66              :     List       *elemlist;
      67              :     ListCell   *l;
      68         4712 :     int         extrachecks = 0;
      69              :     int        *myextra;
      70              : 
      71         4712 :     if (pg_strcasecmp(*newvalue, "all") == 0)
      72            8 :         extrachecks = PLPGSQL_XCHECK_ALL;
      73         4704 :     else if (pg_strcasecmp(*newvalue, "none") == 0)
      74         4676 :         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         4712 :     myextra = (int *) guc_malloc(LOG, sizeof(int));
     121         4712 :     if (!myextra)
     122            0 :         return false;
     123         4712 :     *myextra = extrachecks;
     124         4712 :     *extra = myextra;
     125              : 
     126         4712 :     return true;
     127              : }
     128              : 
     129              : static void
     130         2370 : plpgsql_extra_warnings_assign_hook(const char *newvalue, void *extra)
     131              : {
     132         2370 :     plpgsql_extra_warnings = *((int *) extra);
     133         2370 : }
     134              : 
     135              : static void
     136         2366 : plpgsql_extra_errors_assign_hook(const char *newvalue, void *extra)
     137              : {
     138         2366 :     plpgsql_extra_errors = *((int *) extra);
     139         2366 : }
     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         2334 : _PG_init(void)
     149              : {
     150              :     /* Be sure we do initialization only once (should be redundant now) */
     151              :     static bool inited = false;
     152              : 
     153         2334 :     if (inited)
     154            0 :         return;
     155              : 
     156         2334 :     pg_bindtextdomain(TEXTDOMAIN);
     157              : 
     158         2334 :     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         2334 :     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         2334 :     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         2334 :     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         2334 :     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         2334 :     MarkGUCPrefixReserved("plpgsql");
     204              : 
     205         2334 :     RegisterXactCallback(plpgsql_xact_cb, NULL);
     206         2334 :     RegisterSubXactCallback(plpgsql_subxact_cb, NULL);
     207              : 
     208              :     /* Set up a rendezvous point with optional instrumentation plugin */
     209         2334 :     plpgsql_plugin_ptr = (PLpgSQL_plugin **) find_rendezvous_variable("PLpgSQL_plugin");
     210              : 
     211         2334 :     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          662 : PG_FUNCTION_INFO_V1(plpgsql_call_handler);
     222              : 
     223              : Datum
     224        54462 : 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        54462 :     volatile Datum retval = (Datum) 0;
     231              :     int         rc;
     232              : 
     233       120729 :     nonatomic = fcinfo->context &&
     234        54570 :         IsA(fcinfo->context, CallContext) &&
     235          108 :         !castNode(CallContext, fcinfo->context)->atomic;
     236              : 
     237              :     /*
     238              :      * Connect to SPI manager
     239              :      */
     240        54462 :     SPI_connect_ext(nonatomic ? SPI_OPT_NONATOMIC : 0);
     241              : 
     242              :     /* Find or compile the function */
     243        54462 :     func = plpgsql_compile(fcinfo, false);
     244              : 
     245              :     /* Must save and restore prior value of cur_estate */
     246        54453 :     save_cur_estate = func->cur_estate;
     247              : 
     248              :     /* Mark the function as busy, so it can't be deleted from under us */
     249        54453 :     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        54453 :     procedure_resowner =
     259           96 :         (nonatomic && func->requires_procedure_resowner) ?
     260        54549 :         ResourceOwnerCreate(NULL, "PL/pgSQL procedure resources") : NULL;
     261              : 
     262        54453 :     PG_TRY();
     263              :     {
     264              :         /*
     265              :          * Determine if called as function or trigger and call appropriate
     266              :          * subhandler
     267              :          */
     268        54453 :         if (CALLED_AS_TRIGGER(fcinfo))
     269        10314 :             retval = PointerGetDatum(plpgsql_exec_trigger(func,
     270              :                                                           (TriggerData *) fcinfo->context));
     271        44139 :         else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
     272              :         {
     273         1175 :             plpgsql_exec_event_trigger(func,
     274         1175 :                                        (EventTriggerData *) fcinfo->context);
     275              :             /* there's no return value in this case */
     276              :         }
     277              :         else
     278        42558 :             retval = plpgsql_exec_function(func, fcinfo,
     279              :                                            NULL, NULL,
     280              :                                            procedure_resowner,
     281        42964 :                                            !nonatomic);
     282              :     }
     283          560 :     PG_FINALLY();
     284              :     {
     285              :         /* Decrement use-count, restore cur_estate */
     286        54453 :         func->cfunc.use_count--;
     287        54453 :         func->cur_estate = save_cur_estate;
     288              : 
     289              :         /* Be sure to release the procedure resowner if any */
     290        54453 :         if (procedure_resowner)
     291              :         {
     292           14 :             ReleaseAllPlanCacheRefsInOwner(procedure_resowner);
     293           14 :             ResourceOwnerDelete(procedure_resowner);
     294              :         }
     295              :     }
     296        54453 :     PG_END_TRY();
     297              : 
     298              :     /*
     299              :      * Disconnect from SPI manager
     300              :      */
     301        53893 :     if ((rc = SPI_finish()) != SPI_OK_FINISH)
     302            0 :         elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
     303              : 
     304        53893 :     return retval;
     305              : }
     306              : 
     307              : /* ----------
     308              :  * plpgsql_inline_handler
     309              :  *
     310              :  * Called by PostgreSQL to execute an anonymous code block
     311              :  * ----------
     312              :  */
     313          217 : PG_FUNCTION_INFO_V1(plpgsql_inline_handler);
     314              : 
     315              : Datum
     316          837 : plpgsql_inline_handler(PG_FUNCTION_ARGS)
     317              : {
     318          837 :     LOCAL_FCINFO(fake_fcinfo, 0);
     319          837 :     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          837 :     SPI_connect_ext(codeblock->atomic ? 0 : SPI_OPT_NONATOMIC);
     331              : 
     332              :     /* Compile the anonymous code block */
     333          837 :     func = plpgsql_compile_inline(codeblock->source_text);
     334              : 
     335              :     /* Mark the function as busy, just pro forma */
     336          808 :     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         4040 :     MemSet(fake_fcinfo, 0, SizeForFunctionCallInfo(0));
     344         5656 :     MemSet(&flinfo, 0, sizeof(flinfo));
     345          808 :     fake_fcinfo->flinfo = &flinfo;
     346          808 :     flinfo.fn_oid = InvalidOid;
     347          808 :     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          808 :     simple_eval_estate = CreateExecutorState();
     362              :     simple_eval_resowner =
     363          808 :         ResourceOwnerCreate(NULL, "PL/pgSQL DO block simple expressions");
     364              : 
     365              :     /* And run the function */
     366          808 :     PG_TRY();
     367              :     {
     368          622 :         retval = plpgsql_exec_function(func, fake_fcinfo,
     369              :                                        simple_eval_estate,
     370              :                                        simple_eval_resowner,
     371              :                                        simple_eval_resowner,    /* see above */
     372          808 :                                        codeblock->atomic);
     373              :     }
     374          186 :     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          186 :         plpgsql_subxact_cb(SUBXACT_EVENT_ABORT_SUB,
     391              :                            GetCurrentSubTransactionId(),
     392              :                            0, NULL);
     393              : 
     394              :         /* Clean up the private EState and resowner */
     395          186 :         FreeExecutorState(simple_eval_estate);
     396          186 :         ReleaseAllPlanCacheRefsInOwner(simple_eval_resowner);
     397          186 :         ResourceOwnerDelete(simple_eval_resowner);
     398              : 
     399              :         /* Function should now have no remaining use-counts ... */
     400          186 :         func->cfunc.use_count--;
     401              :         Assert(func->cfunc.use_count == 0);
     402              : 
     403              :         /* ... so we can free subsidiary storage */
     404          186 :         plpgsql_free_function_memory(func);
     405              : 
     406              :         /* And propagate the error */
     407          186 :         PG_RE_THROW();
     408              :     }
     409          622 :     PG_END_TRY();
     410              : 
     411              :     /* Clean up the private EState and resowner */
     412          622 :     FreeExecutorState(simple_eval_estate);
     413          622 :     ReleaseAllPlanCacheRefsInOwner(simple_eval_resowner);
     414          622 :     ResourceOwnerDelete(simple_eval_resowner);
     415              : 
     416              :     /* Function should now have no remaining use-counts ... */
     417          622 :     func->cfunc.use_count--;
     418              :     Assert(func->cfunc.use_count == 0);
     419              : 
     420              :     /* ... so we can free subsidiary storage */
     421          622 :     plpgsql_free_function_memory(func);
     422              : 
     423              :     /*
     424              :      * Disconnect from SPI manager
     425              :      */
     426          622 :     if ((rc = SPI_finish()) != SPI_OK_FINISH)
     427            0 :         elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
     428              : 
     429          622 :     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          467 : PG_FUNCTION_INFO_V1(plpgsql_validator);
     440              : 
     441              : Datum
     442         2913 : plpgsql_validator(PG_FUNCTION_ARGS)
     443              : {
     444         2913 :     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         2913 :     bool        is_dml_trigger = false;
     453         2913 :     bool        is_event_trigger = false;
     454              :     int         i;
     455              : 
     456         2913 :     if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
     457            0 :         PG_RETURN_VOID();
     458              : 
     459              :     /* Get the new function's pg_proc entry */
     460         2913 :     tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
     461         2913 :     if (!HeapTupleIsValid(tuple))
     462            0 :         elog(ERROR, "cache lookup failed for function %u", funcoid);
     463         2913 :     proc = (Form_pg_proc) GETSTRUCT(tuple);
     464              : 
     465         2913 :     functyptype = get_typtype(proc->prorettype);
     466              : 
     467              :     /* Disallow pseudotype result */
     468              :     /* except for TRIGGER, EVTTRIGGER, RECORD, VOID, or polymorphic */
     469         2913 :     if (functyptype == TYPTYPE_PSEUDO)
     470              :     {
     471         1741 :         if (proc->prorettype == TRIGGEROID)
     472          876 :             is_dml_trigger = true;
     473          865 :         else if (proc->prorettype == EVENT_TRIGGEROID)
     474           91 :             is_event_trigger = true;
     475          774 :         else if (proc->prorettype != RECORDOID &&
     476          656 :                  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         2913 :     numargs = get_func_arg_info(tuple,
     487              :                                 &argtypes, &argnames, &argmodes);
     488         4929 :     for (i = 0; i < numargs; i++)
     489              :     {
     490         2016 :         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         2913 :     if (check_function_bodies)
     503              :     {
     504         2759 :         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         2759 :         SPI_connect();
     514              : 
     515              :         /*
     516              :          * Set up a fake fcinfo with just enough info to satisfy
     517              :          * plpgsql_compile().
     518              :          */
     519        13795 :         MemSet(fake_fcinfo, 0, SizeForFunctionCallInfo(0));
     520        19313 :         MemSet(&flinfo, 0, sizeof(flinfo));
     521         2759 :         fake_fcinfo->flinfo = &flinfo;
     522         2759 :         flinfo.fn_oid = funcoid;
     523         2759 :         flinfo.fn_mcxt = CurrentMemoryContext;
     524         2759 :         if (is_dml_trigger)
     525              :         {
     526         8987 :             MemSet(&trigdata, 0, sizeof(trigdata));
     527          817 :             trigdata.type = T_TriggerData;
     528          817 :             fake_fcinfo->context = (Node *) &trigdata;
     529              :         }
     530         1942 :         else if (is_event_trigger)
     531              :         {
     532          405 :             MemSet(&etrigdata, 0, sizeof(etrigdata));
     533           81 :             etrigdata.type = T_EventTriggerData;
     534           81 :             fake_fcinfo->context = (Node *) &etrigdata;
     535              :         }
     536              : 
     537              :         /* Test-compile the function */
     538         2759 :         plpgsql_compile(fake_fcinfo, true);
     539              : 
     540              :         /*
     541              :          * Disconnect from SPI manager
     542              :          */
     543         2690 :         if ((rc = SPI_finish()) != SPI_OK_FINISH)
     544            0 :             elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
     545              :     }
     546              : 
     547         2844 :     ReleaseSysCache(tuple);
     548              : 
     549         2844 :     PG_RETURN_VOID();
     550              : }
        

Generated by: LCOV version 2.0-1