LCOV - code coverage report
Current view: top level - src/pl/plpython - plpy_main.c (source / functions) Hit Total Coverage
Test: PostgreSQL 19devel Lines: 150 160 93.8 %
Date: 2026-02-02 14:17:46 Functions: 15 15 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 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          46 : _PG_init(void)
      58             : {
      59             :     PyObject   *main_mod;
      60             :     PyObject   *main_dict;
      61             :     PyObject   *GD;
      62             :     PyObject   *plpy_mod;
      63             : 
      64          46 :     pg_bindtextdomain(TEXTDOMAIN);
      65             : 
      66             :     /* Add plpy to table of built-in modules. */
      67          46 :     PyImport_AppendInittab("plpy", PyInit_plpy);
      68             : 
      69             :     /* Initialize Python interpreter. */
      70          46 :     Py_Initialize();
      71             : 
      72          46 :     main_mod = PyImport_AddModule("__main__");
      73          46 :     if (main_mod == NULL || PyErr_Occurred())
      74           0 :         PLy_elog(ERROR, "could not import \"%s\" module", "__main__");
      75          46 :     Py_INCREF(main_mod);
      76             : 
      77          46 :     main_dict = PyModule_GetDict(main_mod);
      78          46 :     if (main_dict == NULL)
      79           0 :         PLy_elog(ERROR, NULL);
      80             : 
      81             :     /*
      82             :      * Set up GD.
      83             :      */
      84          46 :     GD = PyDict_New();
      85          46 :     if (GD == NULL)
      86           0 :         PLy_elog(ERROR, NULL);
      87          46 :     PyDict_SetItemString(main_dict, "GD", GD);
      88             : 
      89             :     /*
      90             :      * Import plpy.
      91             :      */
      92          46 :     plpy_mod = PyImport_ImportModule("plpy");
      93          46 :     if (plpy_mod == NULL)
      94           0 :         PLy_elog(ERROR, "could not import \"%s\" module", "plpy");
      95          46 :     if (PyDict_SetItemString(main_dict, "plpy", plpy_mod) == -1)
      96           0 :         PLy_elog(ERROR, NULL);
      97             : 
      98          46 :     if (PyErr_Occurred())
      99           0 :         PLy_elog(FATAL, "untrapped error in initialization");
     100             : 
     101          46 :     Py_INCREF(main_dict);
     102          46 :     PLy_interp_globals = main_dict;
     103             : 
     104          46 :     Py_DECREF(main_mod);
     105             : 
     106          46 :     init_procedure_caches();
     107             : 
     108          46 :     explicit_subtransactions = NIL;
     109             : 
     110          46 :     PLy_execution_contexts = NULL;
     111          46 : }
     112             : 
     113             : Datum
     114         502 : plpython3_validator(PG_FUNCTION_ARGS)
     115             : {
     116         502 :     Oid         funcoid = PG_GETARG_OID(0);
     117             :     HeapTuple   tuple;
     118             :     Form_pg_proc procStruct;
     119             :     PLyTrigType is_trigger;
     120             : 
     121         502 :     if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
     122           0 :         PG_RETURN_VOID();
     123             : 
     124         502 :     if (!check_function_bodies)
     125           2 :         PG_RETURN_VOID();
     126             : 
     127             :     /* Get the new function's pg_proc entry */
     128         500 :     tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
     129         500 :     if (!HeapTupleIsValid(tuple))
     130           0 :         elog(ERROR, "cache lookup failed for function %u", funcoid);
     131         500 :     procStruct = (Form_pg_proc) GETSTRUCT(tuple);
     132             : 
     133         500 :     is_trigger = PLy_procedure_is_trigger(procStruct);
     134             : 
     135         500 :     ReleaseSysCache(tuple);
     136             : 
     137             :     /* We can't validate triggers against any particular table ... */
     138         500 :     (void) PLy_procedure_get(funcoid, InvalidOid, is_trigger);
     139             : 
     140         498 :     PG_RETURN_VOID();
     141             : }
     142             : 
     143             : Datum
     144        1402 : plpython3_call_handler(PG_FUNCTION_ARGS)
     145             : {
     146             :     bool        nonatomic;
     147             :     Datum       retval;
     148             :     PLyExecutionContext *exec_ctx;
     149             :     ErrorContextCallback plerrcontext;
     150             : 
     151        2938 :     nonatomic = fcinfo->context &&
     152        1418 :         IsA(fcinfo->context, CallContext) &&
     153          16 :         !castNode(CallContext, fcinfo->context)->atomic;
     154             : 
     155             :     /* Note: SPI_finish() happens in plpy_exec.c, which is dubious design */
     156        1402 :     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        1402 :     exec_ctx = PLy_push_execution_context(!nonatomic);
     164             : 
     165        1402 :     PG_TRY();
     166             :     {
     167        1402 :         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        1402 :         plerrcontext.callback = plpython_error_callback;
     177        1402 :         plerrcontext.arg = exec_ctx;
     178        1402 :         plerrcontext.previous = error_context_stack;
     179        1402 :         error_context_stack = &plerrcontext;
     180             : 
     181        1402 :         if (CALLED_AS_TRIGGER(fcinfo))
     182          80 :         {
     183          98 :             Relation    tgrel = ((TriggerData *) fcinfo->context)->tg_relation;
     184             :             HeapTuple   trv;
     185             : 
     186          98 :             proc = PLy_procedure_get(funcoid, RelationGetRelid(tgrel), PLPY_TRIGGER);
     187          98 :             exec_ctx->curr_proc = proc;
     188          98 :             trv = PLy_exec_trigger(fcinfo, proc);
     189          80 :             retval = PointerGetDatum(trv);
     190             :         }
     191        1304 :         else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
     192             :         {
     193          20 :             proc = PLy_procedure_get(funcoid, InvalidOid, PLPY_EVENT_TRIGGER);
     194          20 :             exec_ctx->curr_proc = proc;
     195          20 :             PLy_exec_event_trigger(fcinfo, proc);
     196          20 :             retval = (Datum) 0;
     197             :         }
     198             :         else
     199             :         {
     200        1284 :             proc = PLy_procedure_get(funcoid, InvalidOid, PLPY_NOT_TRIGGER);
     201        1278 :             exec_ctx->curr_proc = proc;
     202        1278 :             retval = PLy_exec_function(fcinfo, proc);
     203             :         }
     204             :     }
     205         186 :     PG_CATCH();
     206             :     {
     207         186 :         PLy_pop_execution_context();
     208         186 :         PyErr_Clear();
     209         186 :         PG_RE_THROW();
     210             :     }
     211        1216 :     PG_END_TRY();
     212             : 
     213             :     /* Destroy the execution context */
     214        1216 :     PLy_pop_execution_context();
     215             : 
     216        1216 :     return retval;
     217             : }
     218             : 
     219             : Datum
     220          42 : plpython3_inline_handler(PG_FUNCTION_ARGS)
     221             : {
     222          42 :     LOCAL_FCINFO(fake_fcinfo, 0);
     223          42 :     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          42 :     SPI_connect_ext(codeblock->atomic ? 0 : SPI_OPT_NONATOMIC);
     231             : 
     232         210 :     MemSet(fcinfo, 0, SizeForFunctionCallInfo(0));
     233         294 :     MemSet(&flinfo, 0, sizeof(flinfo));
     234          42 :     fake_fcinfo->flinfo = &flinfo;
     235          42 :     flinfo.fn_oid = InvalidOid;
     236          42 :     flinfo.fn_mcxt = CurrentMemoryContext;
     237             : 
     238        1764 :     MemSet(&proc, 0, sizeof(PLyProcedure));
     239          42 :     proc.mcxt = AllocSetContextCreate(TopMemoryContext,
     240             :                                       "__plpython_inline_block",
     241             :                                       ALLOCSET_DEFAULT_SIZES);
     242          42 :     proc.pyname = MemoryContextStrdup(proc.mcxt, "__plpython_inline_block");
     243          42 :     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          42 :     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          42 :     exec_ctx = PLy_push_execution_context(codeblock->atomic);
     257             : 
     258          42 :     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          42 :         plerrcontext.callback = plpython_inline_error_callback;
     266          42 :         plerrcontext.arg = exec_ctx;
     267          42 :         plerrcontext.previous = error_context_stack;
     268          42 :         error_context_stack = &plerrcontext;
     269             : 
     270          42 :         PLy_procedure_compile(&proc, codeblock->source_text);
     271          42 :         exec_ctx->curr_proc = &proc;
     272          42 :         PLy_exec_function(fake_fcinfo, &proc);
     273             :     }
     274          22 :     PG_CATCH();
     275             :     {
     276          22 :         PLy_pop_execution_context();
     277          22 :         PLy_procedure_delete(&proc);
     278          22 :         PyErr_Clear();
     279          22 :         PG_RE_THROW();
     280             :     }
     281          20 :     PG_END_TRY();
     282             : 
     283             :     /* Destroy the execution context */
     284          20 :     PLy_pop_execution_context();
     285             : 
     286             :     /* Now clean up the transient procedure we made */
     287          20 :     PLy_procedure_delete(&proc);
     288             : 
     289          20 :     PG_RETURN_VOID();
     290             : }
     291             : 
     292             : static PLyTrigType
     293         500 : PLy_procedure_is_trigger(Form_pg_proc procStruct)
     294             : {
     295             :     PLyTrigType ret;
     296             : 
     297         500 :     switch (procStruct->prorettype)
     298             :     {
     299          48 :         case TRIGGEROID:
     300          48 :             ret = PLPY_TRIGGER;
     301          48 :             break;
     302           2 :         case EVENT_TRIGGEROID:
     303           2 :             ret = PLPY_EVENT_TRIGGER;
     304           2 :             break;
     305         450 :         default:
     306         450 :             ret = PLPY_NOT_TRIGGER;
     307         450 :             break;
     308             :     }
     309             : 
     310         500 :     return ret;
     311             : }
     312             : 
     313             : static void
     314         932 : plpython_error_callback(void *arg)
     315             : {
     316         932 :     PLyExecutionContext *exec_ctx = (PLyExecutionContext *) arg;
     317             : 
     318         932 :     if (exec_ctx->curr_proc)
     319             :     {
     320         926 :         if (exec_ctx->curr_proc->is_procedure)
     321           8 :             errcontext("PL/Python procedure \"%s\"",
     322             :                        PLy_procedure_name(exec_ctx->curr_proc));
     323             :         else
     324         918 :             errcontext("PL/Python function \"%s\"",
     325             :                        PLy_procedure_name(exec_ctx->curr_proc));
     326             :     }
     327         932 : }
     328             : 
     329             : static void
     330          56 : plpython_inline_error_callback(void *arg)
     331             : {
     332          56 :     errcontext("PL/Python anonymous code block");
     333          56 : }
     334             : 
     335             : PLyExecutionContext *
     336        2856 : PLy_current_execution_context(void)
     337             : {
     338        2856 :     if (PLy_execution_contexts == NULL)
     339           0 :         elog(ERROR, "no Python function is currently executing");
     340             : 
     341        2856 :     return PLy_execution_contexts;
     342             : }
     343             : 
     344             : MemoryContext
     345        1586 : 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        1586 :     if (context->scratch_ctx == NULL)
     352         884 :         context->scratch_ctx =
     353         884 :             AllocSetContextCreate(TopTransactionContext,
     354             :                                   "PL/Python scratch context",
     355             :                                   ALLOCSET_DEFAULT_SIZES);
     356        1586 :     return context->scratch_ctx;
     357             : }
     358             : 
     359             : static PLyExecutionContext *
     360        1444 : 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        1444 :         MemoryContextAlloc(atomic_context ? TopTransactionContext : PortalContext,
     367             :                            sizeof(PLyExecutionContext));
     368        1444 :     context->curr_proc = NULL;
     369        1444 :     context->scratch_ctx = NULL;
     370        1444 :     context->next = PLy_execution_contexts;
     371        1444 :     PLy_execution_contexts = context;
     372        1444 :     return context;
     373             : }
     374             : 
     375             : static void
     376        1444 : PLy_pop_execution_context(void)
     377             : {
     378        1444 :     PLyExecutionContext *context = PLy_execution_contexts;
     379             : 
     380        1444 :     if (context == NULL)
     381           0 :         elog(ERROR, "no Python function is currently executing");
     382             : 
     383        1444 :     PLy_execution_contexts = context->next;
     384             : 
     385        1444 :     if (context->scratch_ctx)
     386         850 :         MemoryContextDelete(context->scratch_ctx);
     387        1444 :     pfree(context);
     388        1444 : }

Generated by: LCOV version 1.16