LCOV - code coverage report
Current view: top level - src/test/modules/plsample - plsample.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 85 109 78.0 %
Date: 2024-04-20 03:11:03 Functions: 5 5 100.0 %
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-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             : 
      28           2 : PG_MODULE_MAGIC;
      29             : 
      30           4 : 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          12 : plsample_call_handler(PG_FUNCTION_ARGS)
      40             : {
      41          12 :     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          12 :     PG_TRY();
      49             :     {
      50             :         /*
      51             :          * Determine if called as function or trigger and call appropriate
      52             :          * subhandler.
      53             :          */
      54          12 :         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           8 :             retval = PointerGetDatum(plsample_trigger_handler(fcinfo));
      62             :         }
      63           4 :         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           4 :             retval = plsample_func_handler(fcinfo);
      77             :         }
      78             :     }
      79           0 :     PG_FINALLY();
      80             :     {
      81             :     }
      82          12 :     PG_END_TRY();
      83             : 
      84          12 :     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           4 : 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           4 :     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. */
     115           4 :     pl_tuple = SearchSysCache1(PROCOID,
     116           4 :                                ObjectIdGetDatum(fcinfo->flinfo->fn_oid));
     117           4 :     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           4 :     pl_struct = (Form_pg_proc) GETSTRUCT(pl_tuple);
     126           4 :     proname = pstrdup(NameStr(pl_struct->proname));
     127           4 :     ret = SysCacheGetAttr(PROCOID, pl_tuple, Anum_pg_proc_prosrc, &isnull);
     128           4 :     if (isnull)
     129           0 :         elog(ERROR, "could not find source text of function \"%s\"",
     130             :              proname);
     131           4 :     source = DatumGetCString(DirectFunctionCall1(textout, ret));
     132           4 :     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           4 :     proc_cxt = AllocSetContextCreate(TopMemoryContext,
     141             :                                      "PL/Sample function",
     142             :                                      ALLOCSET_SMALL_SIZES);
     143             : 
     144           4 :     arg_out_func = (FmgrInfo *) palloc0(fcinfo->nargs * sizeof(FmgrInfo));
     145           4 :     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          12 :     for (int i = 0; i < numargs; i++)
     152             :     {
     153           8 :         Oid         argtype = pl_struct->proargtypes.values[i];
     154             :         char       *value;
     155             : 
     156           8 :         type_tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(argtype));
     157           8 :         if (!HeapTupleIsValid(type_tuple))
     158           0 :             elog(ERROR, "cache lookup failed for type %u", argtype);
     159             : 
     160           8 :         type_struct = (Form_pg_type) GETSTRUCT(type_tuple);
     161           8 :         fmgr_info_cxt(type_struct->typoutput, &(arg_out_func[i]), proc_cxt);
     162           8 :         ReleaseSysCache(type_tuple);
     163             : 
     164           8 :         value = OutputFunctionCall(&arg_out_func[i], fcinfo->args[i].value);
     165           8 :         ereport(NOTICE,
     166             :                 (errmsg("argument: %d; name: %s; value: %s",
     167             :                         i, argnames[i], value)));
     168             :     }
     169             : 
     170             :     /* Type of the result */
     171           4 :     prorettype = pl_struct->prorettype;
     172           4 :     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           4 :     if (prorettype != TEXTOID)
     183           2 :         PG_RETURN_NULL();
     184             : 
     185           2 :     type_tuple = SearchSysCache1(TYPEOID,
     186             :                                  ObjectIdGetDatum(prorettype));
     187           2 :     if (!HeapTupleIsValid(type_tuple))
     188           0 :         elog(ERROR, "cache lookup failed for type %u", prorettype);
     189           2 :     pg_type_entry = (Form_pg_type) GETSTRUCT(type_tuple);
     190           2 :     result_typioparam = getTypeIOParam(type_tuple);
     191             : 
     192           2 :     fmgr_info_cxt(pg_type_entry->typinput, &result_in_func, proc_cxt);
     193           2 :     ReleaseSysCache(type_tuple);
     194             : 
     195           2 :     ret = InputFunctionCall(&result_in_func, source, result_typioparam, -1);
     196           2 :     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
     205           8 : plsample_trigger_handler(PG_FUNCTION_ARGS)
     206             : {
     207           8 :     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           8 :     if (!CALLED_AS_TRIGGER(fcinfo))
     220           0 :         elog(ERROR, "not called by trigger manager");
     221             : 
     222             :     /* Connect to the SPI manager */
     223           8 :     if (SPI_connect() != SPI_OK_CONNECT)
     224           0 :         elog(ERROR, "could not connect to SPI manager");
     225             : 
     226           8 :     rc = SPI_register_trigger_data(trigdata);
     227             :     Assert(rc >= 0);
     228             : 
     229             :     /* Fetch the function's pg_proc entry. */
     230           8 :     pl_tuple = SearchSysCache1(PROCOID,
     231           8 :                                ObjectIdGetDatum(fcinfo->flinfo->fn_oid));
     232           8 :     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           8 :     pl_struct = (Form_pg_proc) GETSTRUCT(pl_tuple);
     243           8 :     proname = pstrdup(NameStr(pl_struct->proname));
     244           8 :     ret = SysCacheGetAttr(PROCOID, pl_tuple, Anum_pg_proc_prosrc, &isnull);
     245           8 :     if (isnull)
     246           0 :         elog(ERROR, "could not find source text of function \"%s\"",
     247             :              proname);
     248           8 :     source = DatumGetCString(DirectFunctionCall1(textout, ret));
     249           8 :     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           8 :     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           8 :     PG_TRY();
     284             :     {
     285           8 :         ereport(NOTICE,
     286             :                 (errmsg("trigger name: %s", trigdata->tg_trigger->tgname)));
     287           8 :         string = SPI_getrelname(trigdata->tg_relation);
     288           8 :         ereport(NOTICE, (errmsg("trigger relation: %s", string)));
     289             : 
     290           8 :         string = SPI_getnspname(trigdata->tg_relation);
     291           8 :         ereport(NOTICE, (errmsg("trigger relation schema: %s", string)));
     292             : 
     293             :         /* Example handling of different trigger aspects. */
     294             : 
     295           8 :         if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
     296             :         {
     297           4 :             ereport(NOTICE, (errmsg("triggered by INSERT")));
     298           4 :             rettup = trigdata->tg_trigtuple;
     299             :         }
     300           4 :         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           4 :         else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
     306             :         {
     307           4 :             ereport(NOTICE, (errmsg("triggered by UPDATE")));
     308           4 :             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           8 :         if (TRIGGER_FIRED_BEFORE(trigdata->tg_event))
     319           4 :             ereport(NOTICE, (errmsg("triggered BEFORE")));
     320           4 :         else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
     321           4 :             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           8 :         if (TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
     328           8 :             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          12 :         for (int i = 0; i < trigdata->tg_trigger->tgnargs; i++)
     339           4 :             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           8 :     PG_END_TRY();
     349             : 
     350           8 :     if (SPI_finish() != SPI_OK_FINISH)
     351           0 :         elog(ERROR, "SPI_finish() failed");
     352             : 
     353           8 :     return rettup;
     354             : }

Generated by: LCOV version 1.14