LCOV - code coverage report
Current view: top level - src/pl/plpython - plpy_main.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 93.6 % 157 147
Test Date: 2026-03-14 12:15:02 Functions: 100.0 % 15 15
Legend: Lines:     hit not hit

            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 "access/htup_details.h"
      10              : #include "catalog/pg_proc.h"
      11              : #include "catalog/pg_type.h"
      12              : #include "commands/event_trigger.h"
      13              : #include "commands/trigger.h"
      14              : #include "executor/spi.h"
      15              : #include "miscadmin.h"
      16              : #include "plpy_elog.h"
      17              : #include "plpy_exec.h"
      18              : #include "plpy_main.h"
      19              : #include "plpy_plpymodule.h"
      20              : #include "plpy_procedure.h"
      21              : #include "plpy_subxactobject.h"
      22              : #include "plpy_util.h"
      23              : #include "utils/guc.h"
      24              : #include "utils/memutils.h"
      25              : #include "utils/rel.h"
      26              : #include "utils/syscache.h"
      27              : 
      28              : /*
      29              :  * exported functions
      30              :  */
      31              : 
      32           23 : PG_MODULE_MAGIC_EXT(
      33              :                     .name = "plpython",
      34              :                     .version = PG_VERSION
      35              : );
      36              : 
      37           26 : PG_FUNCTION_INFO_V1(plpython3_validator);
      38           26 : PG_FUNCTION_INFO_V1(plpython3_call_handler);
      39            8 : PG_FUNCTION_INFO_V1(plpython3_inline_handler);
      40              : 
      41              : 
      42              : static PLyTrigType PLy_procedure_is_trigger(Form_pg_proc procStruct);
      43              : static void plpython_error_callback(void *arg);
      44              : static void plpython_inline_error_callback(void *arg);
      45              : 
      46              : static PLyExecutionContext *PLy_push_execution_context(bool atomic_context);
      47              : static void PLy_pop_execution_context(void);
      48              : 
      49              : /* initialize global variables */
      50              : PyObject   *PLy_interp_globals = NULL;
      51              : 
      52              : /* this doesn't need to be global; use PLy_current_execution_context() */
      53              : static PLyExecutionContext *PLy_execution_contexts = NULL;
      54              : 
      55              : 
      56              : void
      57           23 : _PG_init(void)
      58              : {
      59              :     PyObject   *main_mod;
      60              :     PyObject   *main_dict;
      61              :     PyObject   *GD;
      62              :     PyObject   *plpy_mod;
      63              : 
      64           23 :     pg_bindtextdomain(TEXTDOMAIN);
      65              : 
      66              :     /* Add plpy to table of built-in modules. */
      67           23 :     PyImport_AppendInittab("plpy", PyInit_plpy);
      68              : 
      69              :     /* Initialize Python interpreter. */
      70           23 :     Py_Initialize();
      71              : 
      72           23 :     main_mod = PyImport_AddModule("__main__");
      73           23 :     if (main_mod == NULL || PyErr_Occurred())
      74            0 :         PLy_elog(ERROR, "could not import \"%s\" module", "__main__");
      75              :     Py_INCREF(main_mod);
      76              : 
      77           23 :     main_dict = PyModule_GetDict(main_mod);
      78           23 :     if (main_dict == NULL)
      79            0 :         PLy_elog(ERROR, NULL);
      80              : 
      81              :     /*
      82              :      * Set up GD.
      83              :      */
      84           23 :     GD = PyDict_New();
      85           23 :     if (GD == NULL)
      86            0 :         PLy_elog(ERROR, NULL);
      87           23 :     PyDict_SetItemString(main_dict, "GD", GD);
      88              : 
      89              :     /*
      90              :      * Import plpy.
      91              :      */
      92           23 :     plpy_mod = PyImport_ImportModule("plpy");
      93           23 :     if (plpy_mod == NULL)
      94            0 :         PLy_elog(ERROR, "could not import \"%s\" module", "plpy");
      95           23 :     if (PyDict_SetItemString(main_dict, "plpy", plpy_mod) == -1)
      96            0 :         PLy_elog(ERROR, NULL);
      97              : 
      98           23 :     if (PyErr_Occurred())
      99            0 :         PLy_elog(FATAL, "untrapped error in initialization");
     100              : 
     101              :     Py_INCREF(main_dict);
     102           23 :     PLy_interp_globals = main_dict;
     103              : 
     104              :     Py_DECREF(main_mod);
     105              : 
     106           23 :     init_procedure_caches();
     107              : 
     108           23 :     explicit_subtransactions = NIL;
     109              : 
     110           23 :     PLy_execution_contexts = NULL;
     111           23 : }
     112              : 
     113              : Datum
     114          251 : plpython3_validator(PG_FUNCTION_ARGS)
     115              : {
     116          251 :     Oid         funcoid = PG_GETARG_OID(0);
     117              :     HeapTuple   tuple;
     118              :     Form_pg_proc procStruct;
     119              :     PLyTrigType is_trigger;
     120              : 
     121          251 :     if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
     122            0 :         PG_RETURN_VOID();
     123              : 
     124          251 :     if (!check_function_bodies)
     125            1 :         PG_RETURN_VOID();
     126              : 
     127              :     /* Get the new function's pg_proc entry */
     128          250 :     tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
     129          250 :     if (!HeapTupleIsValid(tuple))
     130            0 :         elog(ERROR, "cache lookup failed for function %u", funcoid);
     131          250 :     procStruct = (Form_pg_proc) GETSTRUCT(tuple);
     132              : 
     133          250 :     is_trigger = PLy_procedure_is_trigger(procStruct);
     134              : 
     135          250 :     ReleaseSysCache(tuple);
     136              : 
     137              :     /* We can't validate triggers against any particular table ... */
     138          250 :     (void) PLy_procedure_get(funcoid, InvalidOid, is_trigger);
     139              : 
     140          249 :     PG_RETURN_VOID();
     141              : }
     142              : 
     143              : Datum
     144          701 : plpython3_call_handler(PG_FUNCTION_ARGS)
     145              : {
     146              :     bool        nonatomic;
     147              :     Datum       retval;
     148              :     PLyExecutionContext *exec_ctx;
     149              :     ErrorContextCallback plerrcontext;
     150              : 
     151         1469 :     nonatomic = fcinfo->context &&
     152          709 :         IsA(fcinfo->context, CallContext) &&
     153            8 :         !castNode(CallContext, fcinfo->context)->atomic;
     154              : 
     155              :     /* Note: SPI_finish() happens in plpy_exec.c, which is dubious design */
     156          701 :     SPI_connect_ext(nonatomic ? SPI_OPT_NONATOMIC : 0);
     157              : 
     158              :     /*
     159              :      * Push execution context onto stack.  It is important that this get
     160              :      * popped again, so avoid putting anything that could throw error between
     161              :      * here and the PG_TRY.
     162              :      */
     163          701 :     exec_ctx = PLy_push_execution_context(!nonatomic);
     164              : 
     165          701 :     PG_TRY();
     166              :     {
     167          701 :         Oid         funcoid = fcinfo->flinfo->fn_oid;
     168              :         PLyProcedure *proc;
     169              : 
     170              :         /*
     171              :          * Setup error traceback support for ereport().  Note that the PG_TRY
     172              :          * structure pops this for us again at exit, so we needn't do that
     173              :          * explicitly, nor do we risk the callback getting called after we've
     174              :          * destroyed the exec_ctx.
     175              :          */
     176          701 :         plerrcontext.callback = plpython_error_callback;
     177          701 :         plerrcontext.arg = exec_ctx;
     178          701 :         plerrcontext.previous = error_context_stack;
     179          701 :         error_context_stack = &plerrcontext;
     180              : 
     181          701 :         if (CALLED_AS_TRIGGER(fcinfo))
     182           40 :         {
     183           49 :             Relation    tgrel = ((TriggerData *) fcinfo->context)->tg_relation;
     184              :             HeapTuple   trv;
     185              : 
     186           49 :             proc = PLy_procedure_get(funcoid, RelationGetRelid(tgrel), PLPY_TRIGGER);
     187           49 :             exec_ctx->curr_proc = proc;
     188           49 :             trv = PLy_exec_trigger(fcinfo, proc);
     189           40 :             retval = PointerGetDatum(trv);
     190              :         }
     191          652 :         else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
     192              :         {
     193           10 :             proc = PLy_procedure_get(funcoid, InvalidOid, PLPY_EVENT_TRIGGER);
     194           10 :             exec_ctx->curr_proc = proc;
     195           10 :             PLy_exec_event_trigger(fcinfo, proc);
     196           10 :             retval = (Datum) 0;
     197              :         }
     198              :         else
     199              :         {
     200          642 :             proc = PLy_procedure_get(funcoid, InvalidOid, PLPY_NOT_TRIGGER);
     201          639 :             exec_ctx->curr_proc = proc;
     202          639 :             retval = PLy_exec_function(fcinfo, proc);
     203              :         }
     204              :     }
     205           93 :     PG_CATCH();
     206              :     {
     207           93 :         PLy_pop_execution_context();
     208           93 :         PyErr_Clear();
     209           93 :         PG_RE_THROW();
     210              :     }
     211          608 :     PG_END_TRY();
     212              : 
     213              :     /* Destroy the execution context */
     214          608 :     PLy_pop_execution_context();
     215              : 
     216          608 :     return retval;
     217              : }
     218              : 
     219              : Datum
     220           21 : plpython3_inline_handler(PG_FUNCTION_ARGS)
     221              : {
     222           21 :     LOCAL_FCINFO(fake_fcinfo, 0);
     223           21 :     InlineCodeBlock *codeblock = (InlineCodeBlock *) DatumGetPointer(PG_GETARG_DATUM(0));
     224              :     FmgrInfo    flinfo;
     225              :     PLyProcedure proc;
     226              :     PLyExecutionContext *exec_ctx;
     227              :     ErrorContextCallback plerrcontext;
     228              : 
     229              :     /* Note: SPI_finish() happens in plpy_exec.c, which is dubious design */
     230           21 :     SPI_connect_ext(codeblock->atomic ? 0 : SPI_OPT_NONATOMIC);
     231              : 
     232          105 :     MemSet(fcinfo, 0, SizeForFunctionCallInfo(0));
     233          147 :     MemSet(&flinfo, 0, sizeof(flinfo));
     234           21 :     fake_fcinfo->flinfo = &flinfo;
     235           21 :     flinfo.fn_oid = InvalidOid;
     236           21 :     flinfo.fn_mcxt = CurrentMemoryContext;
     237              : 
     238          882 :     MemSet(&proc, 0, sizeof(PLyProcedure));
     239           21 :     proc.mcxt = AllocSetContextCreate(TopMemoryContext,
     240              :                                       "__plpython_inline_block",
     241              :                                       ALLOCSET_DEFAULT_SIZES);
     242           21 :     proc.pyname = MemoryContextStrdup(proc.mcxt, "__plpython_inline_block");
     243           21 :     proc.langid = codeblock->langOid;
     244              : 
     245              :     /*
     246              :      * This is currently sufficient to get PLy_exec_function to work, but
     247              :      * someday we might need to be honest and use PLy_output_setup_func.
     248              :      */
     249           21 :     proc.result.typoid = VOIDOID;
     250              : 
     251              :     /*
     252              :      * Push execution context onto stack.  It is important that this get
     253              :      * popped again, so avoid putting anything that could throw error between
     254              :      * here and the PG_TRY.
     255              :      */
     256           21 :     exec_ctx = PLy_push_execution_context(codeblock->atomic);
     257              : 
     258           21 :     PG_TRY();
     259              :     {
     260              :         /*
     261              :          * Setup error traceback support for ereport().
     262              :          * plpython_inline_error_callback doesn't currently need exec_ctx, but
     263              :          * for consistency with plpython3_call_handler we do it the same way.
     264              :          */
     265           21 :         plerrcontext.callback = plpython_inline_error_callback;
     266           21 :         plerrcontext.arg = exec_ctx;
     267           21 :         plerrcontext.previous = error_context_stack;
     268           21 :         error_context_stack = &plerrcontext;
     269              : 
     270           21 :         PLy_procedure_compile(&proc, codeblock->source_text);
     271           21 :         exec_ctx->curr_proc = &proc;
     272           21 :         PLy_exec_function(fake_fcinfo, &proc);
     273              :     }
     274           11 :     PG_CATCH();
     275              :     {
     276           11 :         PLy_pop_execution_context();
     277           11 :         PLy_procedure_delete(&proc);
     278           11 :         PyErr_Clear();
     279           11 :         PG_RE_THROW();
     280              :     }
     281           10 :     PG_END_TRY();
     282              : 
     283              :     /* Destroy the execution context */
     284           10 :     PLy_pop_execution_context();
     285              : 
     286              :     /* Now clean up the transient procedure we made */
     287           10 :     PLy_procedure_delete(&proc);
     288              : 
     289           10 :     PG_RETURN_VOID();
     290              : }
     291              : 
     292              : static PLyTrigType
     293          250 : PLy_procedure_is_trigger(Form_pg_proc procStruct)
     294              : {
     295              :     PLyTrigType ret;
     296              : 
     297          250 :     switch (procStruct->prorettype)
     298              :     {
     299           24 :         case TRIGGEROID:
     300           24 :             ret = PLPY_TRIGGER;
     301           24 :             break;
     302            1 :         case EVENT_TRIGGEROID:
     303            1 :             ret = PLPY_EVENT_TRIGGER;
     304            1 :             break;
     305          225 :         default:
     306          225 :             ret = PLPY_NOT_TRIGGER;
     307          225 :             break;
     308              :     }
     309              : 
     310          250 :     return ret;
     311              : }
     312              : 
     313              : static void
     314          466 : plpython_error_callback(void *arg)
     315              : {
     316          466 :     PLyExecutionContext *exec_ctx = (PLyExecutionContext *) arg;
     317              : 
     318          466 :     if (exec_ctx->curr_proc)
     319              :     {
     320          463 :         if (exec_ctx->curr_proc->is_procedure)
     321            4 :             errcontext("PL/Python procedure \"%s\"",
     322              :                        PLy_procedure_name(exec_ctx->curr_proc));
     323              :         else
     324          459 :             errcontext("PL/Python function \"%s\"",
     325              :                        PLy_procedure_name(exec_ctx->curr_proc));
     326              :     }
     327          466 : }
     328              : 
     329              : static void
     330           28 : plpython_inline_error_callback(void *arg)
     331              : {
     332           28 :     errcontext("PL/Python anonymous code block");
     333           28 : }
     334              : 
     335              : PLyExecutionContext *
     336         1428 : PLy_current_execution_context(void)
     337              : {
     338         1428 :     if (PLy_execution_contexts == NULL)
     339            0 :         elog(ERROR, "no Python function is currently executing");
     340              : 
     341         1428 :     return PLy_execution_contexts;
     342              : }
     343              : 
     344              : MemoryContext
     345          793 : PLy_get_scratch_context(PLyExecutionContext *context)
     346              : {
     347              :     /*
     348              :      * A scratch context might never be needed in a given plpython procedure,
     349              :      * so allocate it on first request.
     350              :      */
     351          793 :     if (context->scratch_ctx == NULL)
     352          442 :         context->scratch_ctx =
     353          442 :             AllocSetContextCreate(TopTransactionContext,
     354              :                                   "PL/Python scratch context",
     355              :                                   ALLOCSET_DEFAULT_SIZES);
     356          793 :     return context->scratch_ctx;
     357              : }
     358              : 
     359              : static PLyExecutionContext *
     360          722 : PLy_push_execution_context(bool atomic_context)
     361              : {
     362              :     PLyExecutionContext *context;
     363              : 
     364              :     /* Pick a memory context similar to what SPI uses. */
     365              :     context = (PLyExecutionContext *)
     366          722 :         MemoryContextAlloc(atomic_context ? TopTransactionContext : PortalContext,
     367              :                            sizeof(PLyExecutionContext));
     368          722 :     context->curr_proc = NULL;
     369          722 :     context->scratch_ctx = NULL;
     370          722 :     context->next = PLy_execution_contexts;
     371          722 :     PLy_execution_contexts = context;
     372          722 :     return context;
     373              : }
     374              : 
     375              : static void
     376          722 : PLy_pop_execution_context(void)
     377              : {
     378          722 :     PLyExecutionContext *context = PLy_execution_contexts;
     379              : 
     380          722 :     if (context == NULL)
     381            0 :         elog(ERROR, "no Python function is currently executing");
     382              : 
     383          722 :     PLy_execution_contexts = context->next;
     384              : 
     385          722 :     if (context->scratch_ctx)
     386          425 :         MemoryContextDelete(context->scratch_ctx);
     387          722 :     pfree(context);
     388          722 : }
        

Generated by: LCOV version 2.0-1