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

Generated by: LCOV version 1.16