LCOV - code coverage report
Current view: top level - src/pl/plpython - plpy_main.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 94.1 % 169 159
Test Date: 2026-07-18 10:15:36 Functions: 100.0 % 15 15
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 65.8 % 149 98

             Branch data     Line data    Source code
       1                 :             : /*
       2                 :             :  * PL/Python main entry points
       3                 :             :  *
       4                 :             :  * src/pl/plpython/plpy_main.c
       5                 :             :  */
       6                 :             : 
       7                 :             : #include "postgres.h"
       8                 :             : 
       9                 :             : #include "catalog/pg_proc.h"
      10                 :             : #include "commands/event_trigger.h"
      11                 :             : #include "commands/trigger.h"
      12                 :             : #include "executor/spi.h"
      13                 :             : #include "miscadmin.h"
      14                 :             : #include "plpy_elog.h"
      15                 :             : #include "plpy_exec.h"
      16                 :             : #include "plpy_main.h"
      17                 :             : #include "plpy_plpymodule.h"
      18                 :             : #include "plpy_subxactobject.h"
      19                 :             : #include "plpy_util.h"
      20                 :             : #include "utils/guc.h"
      21                 :             : #include "utils/memutils.h"
      22                 :             : #include "utils/syscache.h"
      23                 :             : 
      24                 :             : /*
      25                 :             :  * exported functions
      26                 :             :  */
      27                 :             : 
      28                 :          23 : PG_MODULE_MAGIC_EXT(
      29                 :             :                     .name = "plpython",
      30                 :             :                     .version = PG_VERSION
      31                 :             : );
      32                 :             : 
      33                 :          26 : PG_FUNCTION_INFO_V1(plpython3_validator);
      34                 :          26 : PG_FUNCTION_INFO_V1(plpython3_call_handler);
      35                 :           8 : PG_FUNCTION_INFO_V1(plpython3_inline_handler);
      36                 :             : 
      37                 :             : 
      38                 :             : static PLyTrigType PLy_procedure_is_trigger(Form_pg_proc procStruct);
      39                 :             : static void plpython_error_callback(void *arg);
      40                 :             : static void plpython_inline_error_callback(void *arg);
      41                 :             : 
      42                 :             : static PLyExecutionContext *PLy_push_execution_context(bool atomic_context);
      43                 :             : static void PLy_pop_execution_context(void);
      44                 :             : 
      45                 :             : /* initialize global variables */
      46                 :             : PyObject   *PLy_interp_globals = NULL;
      47                 :             : 
      48                 :             : /* this doesn't need to be global; use PLy_current_execution_context() */
      49                 :             : static PLyExecutionContext *PLy_execution_contexts = NULL;
      50                 :             : 
      51                 :             : 
      52                 :             : void
      53                 :          23 : _PG_init(void)
      54                 :             : {
      55                 :             :     PyObject   *main_mod;
      56                 :             :     PyObject   *main_dict;
      57                 :             :     PyObject   *GD;
      58                 :             :     PyObject   *plpy_mod;
      59                 :             : 
      60                 :          23 :     pg_bindtextdomain(TEXTDOMAIN);
      61                 :             : 
      62                 :             :     /* Add plpy to table of built-in modules. */
      63                 :          23 :     PyImport_AppendInittab("plpy", PyInit_plpy);
      64                 :             : 
      65                 :             :     /* Initialize Python interpreter. */
      66                 :          23 :     Py_Initialize();
      67                 :             : 
      68                 :          23 :     main_mod = PyImport_AddModule("__main__");
      69   [ +  -  -  + ]:          23 :     if (main_mod == NULL || PyErr_Occurred())
      70                 :           0 :         PLy_elog(ERROR, "could not import \"%s\" module", "__main__");
      71                 :             :     Py_INCREF(main_mod);
      72                 :             : 
      73                 :          23 :     main_dict = PyModule_GetDict(main_mod);
      74         [ -  + ]:          23 :     if (main_dict == NULL)
      75                 :           0 :         PLy_elog(ERROR, NULL);
      76                 :             : 
      77                 :             :     /*
      78                 :             :      * Set up GD.
      79                 :             :      */
      80                 :          23 :     GD = PyDict_New();
      81         [ -  + ]:          23 :     if (GD == NULL)
      82                 :           0 :         PLy_elog(ERROR, NULL);
      83                 :          23 :     PyDict_SetItemString(main_dict, "GD", GD);
      84                 :             : 
      85                 :             :     /*
      86                 :             :      * Import plpy.
      87                 :             :      */
      88                 :          23 :     plpy_mod = PyImport_ImportModule("plpy");
      89         [ -  + ]:          23 :     if (plpy_mod == NULL)
      90                 :           0 :         PLy_elog(ERROR, "could not import \"%s\" module", "plpy");
      91         [ -  + ]:          23 :     if (PyDict_SetItemString(main_dict, "plpy", plpy_mod) == -1)
      92                 :           0 :         PLy_elog(ERROR, NULL);
      93                 :             : 
      94         [ -  + ]:          23 :     if (PyErr_Occurred())
      95                 :           0 :         PLy_elog(FATAL, "untrapped error in initialization");
      96                 :             : 
      97                 :             :     Py_INCREF(main_dict);
      98                 :          23 :     PLy_interp_globals = main_dict;
      99                 :             : 
     100                 :             :     Py_DECREF(main_mod);
     101                 :             : 
     102                 :          23 :     explicit_subtransactions = NIL;
     103                 :             : 
     104                 :          23 :     PLy_execution_contexts = NULL;
     105                 :          23 : }
     106                 :             : 
     107                 :             : Datum
     108                 :         267 : plpython3_validator(PG_FUNCTION_ARGS)
     109                 :             : {
     110                 :         267 :     LOCAL_FCINFO(fake_fcinfo, 0);
     111                 :         267 :     Oid         funcoid = PG_GETARG_OID(0);
     112                 :             :     HeapTuple   tuple;
     113                 :             :     Form_pg_proc procStruct;
     114                 :             :     PLyTrigType is_trigger;
     115                 :             :     TriggerData trigdata;
     116                 :             :     EventTriggerData etrigdata;
     117                 :             :     FmgrInfo    flinfo;
     118                 :             :     PLyProcedureCache *pcache;
     119                 :             : 
     120         [ -  + ]:         267 :     if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
     121                 :           0 :         PG_RETURN_VOID();
     122                 :             : 
     123         [ +  + ]:         267 :     if (!check_function_bodies)
     124                 :           1 :         PG_RETURN_VOID();
     125                 :             : 
     126                 :             :     /* Get the new function's pg_proc entry */
     127                 :         266 :     tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
     128         [ -  + ]:         266 :     if (!HeapTupleIsValid(tuple))
     129         [ #  # ]:           0 :         elog(ERROR, "cache lookup failed for function %u", funcoid);
     130                 :         266 :     procStruct = (Form_pg_proc) GETSTRUCT(tuple);
     131                 :             : 
     132                 :         266 :     is_trigger = PLy_procedure_is_trigger(procStruct);
     133                 :             : 
     134                 :         266 :     ReleaseSysCache(tuple);
     135                 :             : 
     136                 :             :     /*
     137                 :             :      * Set up a fake flinfo/fcinfo with just enough info to satisfy
     138                 :             :      * PLy_procedure_get().  That function derives the call context (plain
     139                 :             :      * function, DML trigger, or event trigger) from the fcinfo, so we have to
     140                 :             :      * construct matching context here.
     141                 :             :      */
     142   [ +  -  +  -  :        1330 :     MemSet(fake_fcinfo, 0, SizeForFunctionCallInfo(0));
          +  -  +  -  +  
                      + ]
     143   [ +  -  +  -  :        1862 :     MemSet(&flinfo, 0, sizeof(flinfo));
          +  -  +  -  +  
                      + ]
     144                 :         266 :     fake_fcinfo->flinfo = &flinfo;
     145                 :         266 :     flinfo.fn_oid = funcoid;
     146                 :         266 :     flinfo.fn_mcxt = CurrentMemoryContext;
     147                 :             : 
     148         [ +  + ]:         266 :     if (is_trigger == PLPY_TRIGGER)
     149                 :             :     {
     150   [ +  -  +  -  :         264 :         MemSet(&trigdata, 0, sizeof(trigdata));
          +  -  +  -  +  
                      + ]
     151                 :          24 :         trigdata.type = T_TriggerData;
     152                 :             :         /* We can't validate triggers against any particular table ... */
     153                 :          24 :         fake_fcinfo->context = (Node *) &trigdata;
     154                 :             :     }
     155         [ +  + ]:         242 :     else if (is_trigger == PLPY_EVENT_TRIGGER)
     156                 :             :     {
     157   [ +  -  +  -  :           5 :         MemSet(&etrigdata, 0, sizeof(etrigdata));
          +  -  +  -  +  
                      + ]
     158                 :           1 :         etrigdata.type = T_EventTriggerData;
     159                 :           1 :         fake_fcinfo->context = (Node *) &etrigdata;
     160                 :             :     }
     161                 :             : 
     162                 :         266 :     pcache = PLy_procedure_get(fake_fcinfo, true);
     163                 :             : 
     164                 :             :     /*
     165                 :             :      * Release the reference count that PLy_procedure_get acquired; the
     166                 :             :      * PLyProcedure object remains valid for possible future use.  (We could
     167                 :             :      * leave this to be done when the calling memory context is cleaned up,
     168                 :             :      * but it seems neater to do it right away.  Note we mustn't release the
     169                 :             :      * pcache object, since the memory-context reset callback has a reference
     170                 :             :      * to it.)
     171                 :             :      */
     172                 :             :     Assert(pcache->proc->cfunc.use_count > 0);
     173                 :         265 :     pcache->proc->cfunc.use_count--;
     174                 :         265 :     pcache->proc = NULL;
     175                 :             : 
     176                 :         265 :     PG_RETURN_VOID();
     177                 :             : }
     178                 :             : 
     179                 :             : Datum
     180                 :         728 : plpython3_call_handler(PG_FUNCTION_ARGS)
     181                 :             : {
     182                 :             :     bool        nonatomic;
     183                 :             :     Datum       retval;
     184                 :             :     PLyExecutionContext *exec_ctx;
     185                 :             :     ErrorContextCallback plerrcontext;
     186                 :             : 
     187                 :        1523 :     nonatomic = fcinfo->context &&
     188   [ +  +  +  + ]:         736 :         IsA(fcinfo->context, CallContext) &&
     189         [ +  + ]:           8 :         !castNode(CallContext, fcinfo->context)->atomic;
     190                 :             : 
     191                 :             :     /* Note: SPI_finish() happens in plpy_exec.c, which is dubious design */
     192                 :         728 :     SPI_connect_ext(nonatomic ? SPI_OPT_NONATOMIC : 0);
     193                 :             : 
     194                 :             :     /*
     195                 :             :      * Push execution context onto stack.  It is important that this get
     196                 :             :      * popped again, so avoid putting anything that could throw error between
     197                 :             :      * here and the PG_TRY.
     198                 :             :      */
     199                 :         728 :     exec_ctx = PLy_push_execution_context(!nonatomic);
     200                 :             : 
     201         [ +  + ]:         728 :     PG_TRY();
     202                 :             :     {
     203                 :             :         PLyProcedureCache *pcache;
     204                 :             : 
     205                 :             :         /*
     206                 :             :          * Setup error traceback support for ereport().  Note that the PG_TRY
     207                 :             :          * structure pops this for us again at exit, so we needn't do that
     208                 :             :          * explicitly, nor do we risk the callback getting called after we've
     209                 :             :          * destroyed the exec_ctx.
     210                 :             :          */
     211                 :         728 :         plerrcontext.callback = plpython_error_callback;
     212                 :         728 :         plerrcontext.arg = exec_ctx;
     213                 :         728 :         plerrcontext.previous = error_context_stack;
     214                 :         728 :         error_context_stack = &plerrcontext;
     215                 :             : 
     216                 :             :         /*
     217                 :             :          * Look up (and if necessary compile) the procedure.  This can throw
     218                 :             :          * an error, so it must happen inside the PG_TRY so that the execution
     219                 :             :          * context gets popped on the way out.
     220                 :             :          */
     221                 :         728 :         pcache = PLy_procedure_get(fcinfo, false);
     222                 :         725 :         exec_ctx->curr_proc = pcache->proc;
     223                 :             : 
     224   [ +  +  +  + ]:         725 :         if (CALLED_AS_TRIGGER(fcinfo))
     225                 :          40 :         {
     226                 :             :             HeapTuple   trv;
     227                 :             : 
     228                 :          49 :             trv = PLy_exec_trigger(fcinfo, pcache->proc);
     229                 :          40 :             retval = PointerGetDatum(trv);
     230                 :             :         }
     231   [ +  +  +  + ]:         676 :         else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
     232                 :             :         {
     233                 :          10 :             PLy_exec_event_trigger(fcinfo, pcache->proc);
     234                 :          10 :             retval = (Datum) 0;
     235                 :             :         }
     236                 :             :         else
     237                 :         666 :             retval = PLy_exec_function(fcinfo, pcache);
     238                 :             :     }
     239                 :         107 :     PG_CATCH();
     240                 :             :     {
     241                 :             :         /* Destroy the execution context */
     242                 :         107 :         PLy_pop_execution_context();
     243                 :         107 :         PyErr_Clear();
     244                 :             : 
     245                 :         107 :         PG_RE_THROW();
     246                 :             :     }
     247         [ -  + ]:         621 :     PG_END_TRY();
     248                 :             : 
     249                 :             :     /* Destroy the execution context */
     250                 :         621 :     PLy_pop_execution_context();
     251                 :             : 
     252                 :         621 :     return retval;
     253                 :             : }
     254                 :             : 
     255                 :             : Datum
     256                 :          21 : plpython3_inline_handler(PG_FUNCTION_ARGS)
     257                 :             : {
     258                 :          21 :     LOCAL_FCINFO(fake_fcinfo, 0);
     259                 :          21 :     InlineCodeBlock *codeblock = (InlineCodeBlock *) DatumGetPointer(PG_GETARG_DATUM(0));
     260                 :             :     FmgrInfo    flinfo;
     261                 :             :     PLyProcedure proc;
     262                 :             :     PLyProcedureCache pcache;
     263                 :             :     PLyExecutionContext *exec_ctx;
     264                 :             :     ErrorContextCallback plerrcontext;
     265                 :             : 
     266                 :             :     /* Note: SPI_finish() happens in plpy_exec.c, which is dubious design */
     267                 :          21 :     SPI_connect_ext(codeblock->atomic ? 0 : SPI_OPT_NONATOMIC);
     268                 :             : 
     269   [ +  -  +  -  :         105 :     MemSet(fcinfo, 0, SizeForFunctionCallInfo(0));
          +  -  +  -  +  
                      + ]
     270   [ +  -  +  -  :         147 :     MemSet(&flinfo, 0, sizeof(flinfo));
          +  -  +  -  +  
                      + ]
     271                 :          21 :     fake_fcinfo->flinfo = &flinfo;
     272                 :          21 :     flinfo.fn_oid = InvalidOid;
     273                 :          21 :     flinfo.fn_mcxt = CurrentMemoryContext;
     274                 :             : 
     275   [ +  -  +  -  :         945 :     MemSet(&proc, 0, sizeof(PLyProcedure));
          +  -  +  -  +  
                      + ]
     276                 :          21 :     proc.mcxt = AllocSetContextCreate(TopMemoryContext,
     277                 :             :                                       "__plpython_inline_block",
     278                 :             :                                       ALLOCSET_DEFAULT_SIZES);
     279                 :          21 :     proc.pyname = MemoryContextStrdup(proc.mcxt, "__plpython_inline_block");
     280                 :          21 :     proc.langid = codeblock->langOid;
     281                 :             : 
     282                 :             :     /*
     283                 :             :      * This is currently sufficient to get PLy_exec_function to work, but
     284                 :             :      * someday we might need to be honest and use PLy_output_setup_func.
     285                 :             :      */
     286                 :          21 :     proc.result.typoid = VOIDOID;
     287                 :             : 
     288                 :             :     /* Set up a minimal PLyProcedureCache for the inline block */
     289   [ +  -  +  -  :         168 :     MemSet(&pcache, 0, sizeof(PLyProcedureCache));
          +  -  +  -  +  
                      + ]
     290                 :          21 :     pcache.proc = &proc;
     291                 :          21 :     pcache.fcontext = CurrentMemoryContext;
     292                 :             : 
     293                 :             :     /*
     294                 :             :      * Push execution context onto stack.  It is important that this get
     295                 :             :      * popped again, so avoid putting anything that could throw error between
     296                 :             :      * here and the PG_TRY.
     297                 :             :      */
     298                 :          21 :     exec_ctx = PLy_push_execution_context(codeblock->atomic);
     299                 :             : 
     300         [ +  + ]:          21 :     PG_TRY();
     301                 :             :     {
     302                 :             :         /*
     303                 :             :          * Setup error traceback support for ereport().
     304                 :             :          * plpython_inline_error_callback doesn't currently need exec_ctx, but
     305                 :             :          * for consistency with plpython3_call_handler we do it the same way.
     306                 :             :          */
     307                 :          21 :         plerrcontext.callback = plpython_inline_error_callback;
     308                 :          21 :         plerrcontext.arg = exec_ctx;
     309                 :          21 :         plerrcontext.previous = error_context_stack;
     310                 :          21 :         error_context_stack = &plerrcontext;
     311                 :             : 
     312                 :          21 :         PLy_procedure_compile(&proc, codeblock->source_text);
     313                 :          21 :         exec_ctx->curr_proc = &proc;
     314                 :          21 :         PLy_exec_function(fake_fcinfo, &pcache);
     315                 :             :     }
     316                 :          11 :     PG_CATCH();
     317                 :             :     {
     318                 :          11 :         PLy_pop_execution_context();
     319                 :          11 :         PLy_procedure_delete(&proc);
     320                 :          11 :         PyErr_Clear();
     321                 :          11 :         PG_RE_THROW();
     322                 :             :     }
     323         [ -  + ]:          10 :     PG_END_TRY();
     324                 :             : 
     325                 :             :     /* Destroy the execution context */
     326                 :          10 :     PLy_pop_execution_context();
     327                 :             : 
     328                 :             :     /* Now clean up the transient procedure we made */
     329                 :          10 :     PLy_procedure_delete(&proc);
     330                 :             : 
     331                 :          10 :     PG_RETURN_VOID();
     332                 :             : }
     333                 :             : 
     334                 :             : /*
     335                 :             :  * Determine whether a function is a (DML or event) trigger from its pg_proc
     336                 :             :  * result type.  This is used by the validator, which has no call context to
     337                 :             :  * inspect; the call handler instead relies on the fcinfo's call context.
     338                 :             :  */
     339                 :             : static PLyTrigType
     340                 :         266 : PLy_procedure_is_trigger(Form_pg_proc procStruct)
     341                 :             : {
     342                 :             :     PLyTrigType ret;
     343                 :             : 
     344      [ +  +  + ]:         266 :     switch (procStruct->prorettype)
     345                 :             :     {
     346                 :          24 :         case TRIGGEROID:
     347                 :          24 :             ret = PLPY_TRIGGER;
     348                 :          24 :             break;
     349                 :           1 :         case EVENT_TRIGGEROID:
     350                 :           1 :             ret = PLPY_EVENT_TRIGGER;
     351                 :           1 :             break;
     352                 :         241 :         default:
     353                 :         241 :             ret = PLPY_NOT_TRIGGER;
     354                 :         241 :             break;
     355                 :             :     }
     356                 :             : 
     357                 :         266 :     return ret;
     358                 :             : }
     359                 :             : 
     360                 :             : static void
     361                 :         483 : plpython_error_callback(void *arg)
     362                 :             : {
     363                 :         483 :     PLyExecutionContext *exec_ctx = (PLyExecutionContext *) arg;
     364                 :             : 
     365         [ +  + ]:         483 :     if (exec_ctx->curr_proc)
     366                 :             :     {
     367         [ +  + ]:         480 :         if (exec_ctx->curr_proc->is_procedure)
     368                 :           4 :             errcontext("PL/Python procedure \"%s\"",
     369                 :             :                        PLy_procedure_name(exec_ctx->curr_proc));
     370                 :             :         else
     371                 :         476 :             errcontext("PL/Python function \"%s\"",
     372                 :             :                        PLy_procedure_name(exec_ctx->curr_proc));
     373                 :             :     }
     374                 :         483 : }
     375                 :             : 
     376                 :             : static void
     377                 :          28 : plpython_inline_error_callback(void *arg)
     378                 :             : {
     379                 :          28 :     errcontext("PL/Python anonymous code block");
     380                 :          28 : }
     381                 :             : 
     382                 :             : PLyExecutionContext *
     383                 :        1464 : PLy_current_execution_context(void)
     384                 :             : {
     385         [ -  + ]:        1464 :     if (PLy_execution_contexts == NULL)
     386         [ #  # ]:           0 :         elog(ERROR, "no Python function is currently executing");
     387                 :             : 
     388                 :        1464 :     return PLy_execution_contexts;
     389                 :             : }
     390                 :             : 
     391                 :             : MemoryContext
     392                 :         803 : PLy_get_scratch_context(PLyExecutionContext *context)
     393                 :             : {
     394                 :             :     /*
     395                 :             :      * A scratch context might never be needed in a given plpython procedure,
     396                 :             :      * so allocate it on first request.
     397                 :             :      */
     398         [ +  + ]:         803 :     if (context->scratch_ctx == NULL)
     399                 :         446 :         context->scratch_ctx =
     400                 :         446 :             AllocSetContextCreate(TopTransactionContext,
     401                 :             :                                   "PL/Python scratch context",
     402                 :             :                                   ALLOCSET_DEFAULT_SIZES);
     403                 :         803 :     return context->scratch_ctx;
     404                 :             : }
     405                 :             : 
     406                 :             : static PLyExecutionContext *
     407                 :         749 : PLy_push_execution_context(bool atomic_context)
     408                 :             : {
     409                 :             :     PLyExecutionContext *context;
     410                 :             : 
     411                 :             :     /* Pick a memory context similar to what SPI uses. */
     412                 :             :     context = (PLyExecutionContext *)
     413         [ +  + ]:         749 :         MemoryContextAlloc(atomic_context ? TopTransactionContext : PortalContext,
     414                 :             :                            sizeof(PLyExecutionContext));
     415                 :         749 :     context->curr_proc = NULL;
     416                 :         749 :     context->scratch_ctx = NULL;
     417                 :         749 :     context->next = PLy_execution_contexts;
     418                 :         749 :     PLy_execution_contexts = context;
     419                 :         749 :     return context;
     420                 :             : }
     421                 :             : 
     422                 :             : static void
     423                 :         749 : PLy_pop_execution_context(void)
     424                 :             : {
     425                 :         749 :     PLyExecutionContext *context = PLy_execution_contexts;
     426                 :             : 
     427         [ -  + ]:         749 :     if (context == NULL)
     428         [ #  # ]:           0 :         elog(ERROR, "no Python function is currently executing");
     429                 :             : 
     430                 :         749 :     PLy_execution_contexts = context->next;
     431                 :             : 
     432         [ +  + ]:         749 :     if (context->scratch_ctx)
     433                 :         429 :         MemoryContextDelete(context->scratch_ctx);
     434                 :         749 :     pfree(context);
     435                 :         749 : }
        

Generated by: LCOV version 2.0-1