LCOV - code coverage report
Current view: top level - src/test/modules/plsample - plsample.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 78.7 % 108 85
Test Date: 2026-05-07 04:16:33 Functions: 100.0 % 5 5
Legend: Lines:     hit not hit

            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              : }
        

Generated by: LCOV version 2.0-1