LCOV - code coverage report
Current view: top level - src/pl/plpython - plpy_main.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 148 159 93.1 %
Date: 2024-04-26 10:11:36 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/trigger.h"
      13             : #include "executor/spi.h"
      14             : #include "miscadmin.h"
      15             : #include "plpy_elog.h"
      16             : #include "plpy_exec.h"
      17             : #include "plpy_main.h"
      18             : #include "plpy_plpymodule.h"
      19             : #include "plpy_procedure.h"
      20             : #include "plpy_subxactobject.h"
      21             : #include "plpython.h"
      22             : #include "utils/guc.h"
      23             : #include "utils/memutils.h"
      24             : #include "utils/rel.h"
      25             : #include "utils/syscache.h"
      26             : 
      27             : /*
      28             :  * exported functions
      29             :  */
      30             : 
      31          46 : PG_MODULE_MAGIC;
      32             : 
      33          52 : PG_FUNCTION_INFO_V1(plpython3_validator);
      34          52 : PG_FUNCTION_INFO_V1(plpython3_call_handler);
      35          16 : PG_FUNCTION_INFO_V1(plpython3_inline_handler);
      36             : 
      37             : 
      38             : static bool 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             : static void PLy_init_interp(void);
      42             : 
      43             : static PLyExecutionContext *PLy_push_execution_context(bool atomic_context);
      44             : static void PLy_pop_execution_context(void);
      45             : 
      46             : /* static state for Python library conflict detection */
      47             : static int *plpython_version_bitmask_ptr = NULL;
      48             : static int  plpython_version_bitmask = 0;
      49             : 
      50             : /* initialize global variables */
      51             : PyObject   *PLy_interp_globals = NULL;
      52             : 
      53             : /* this doesn't need to be global; use PLy_current_execution_context() */
      54             : static PLyExecutionContext *PLy_execution_contexts = NULL;
      55             : 
      56             : 
      57             : void
      58          46 : _PG_init(void)
      59             : {
      60             :     int       **bitmask_ptr;
      61             : 
      62             :     /*
      63             :      * Set up a shared bitmask variable telling which Python version(s) are
      64             :      * loaded into this process's address space.  If there's more than one, we
      65             :      * cannot call into libpython for fear of causing crashes.  But postpone
      66             :      * the actual failure for later, so that operations like pg_restore can
      67             :      * load more than one plpython library so long as they don't try to do
      68             :      * anything much with the language.
      69             :      *
      70             :      * While we only support Python 3 these days, somebody might create an
      71             :      * out-of-tree version adding back support for Python 2. Conflicts with
      72             :      * such an extension should be detected.
      73             :      */
      74          46 :     bitmask_ptr = (int **) find_rendezvous_variable("plpython_version_bitmask");
      75          46 :     if (!(*bitmask_ptr))        /* am I the first? */
      76          46 :         *bitmask_ptr = &plpython_version_bitmask;
      77             :     /* Retain pointer to the agreed-on shared variable ... */
      78          46 :     plpython_version_bitmask_ptr = *bitmask_ptr;
      79             :     /* ... and announce my presence */
      80          46 :     *plpython_version_bitmask_ptr |= (1 << PY_MAJOR_VERSION);
      81             : 
      82             :     /*
      83             :      * This should be safe even in the presence of conflicting plpythons, and
      84             :      * it's necessary to do it before possibly throwing a conflict error, or
      85             :      * the error message won't get localized.
      86             :      */
      87          46 :     pg_bindtextdomain(TEXTDOMAIN);
      88          46 : }
      89             : 
      90             : /*
      91             :  * Perform one-time setup of PL/Python, after checking for a conflict
      92             :  * with other versions of Python.
      93             :  */
      94             : static void
      95        1890 : PLy_initialize(void)
      96             : {
      97             :     static bool inited = false;
      98             : 
      99             :     /*
     100             :      * Check for multiple Python libraries before actively doing anything with
     101             :      * libpython.  This must be repeated on each entry to PL/Python, in case a
     102             :      * conflicting library got loaded since we last looked.
     103             :      *
     104             :      * It is attractive to weaken this error from FATAL to ERROR, but there
     105             :      * would be corner cases, so it seems best to be conservative.
     106             :      */
     107        1890 :     if (*plpython_version_bitmask_ptr != (1 << PY_MAJOR_VERSION))
     108           0 :         ereport(FATAL,
     109             :                 (errmsg("multiple Python libraries are present in session"),
     110             :                  errdetail("Only one Python major version can be used in one session.")));
     111             : 
     112             :     /* The rest should only be done once per session */
     113        1890 :     if (inited)
     114        1844 :         return;
     115             : 
     116          46 :     PyImport_AppendInittab("plpy", PyInit_plpy);
     117          46 :     Py_Initialize();
     118          46 :     PyImport_ImportModule("plpy");
     119          46 :     PLy_init_interp();
     120          46 :     PLy_init_plpy();
     121          46 :     if (PyErr_Occurred())
     122           0 :         PLy_elog(FATAL, "untrapped error in initialization");
     123             : 
     124          46 :     init_procedure_caches();
     125             : 
     126          46 :     explicit_subtransactions = NIL;
     127             : 
     128          46 :     PLy_execution_contexts = NULL;
     129             : 
     130          46 :     inited = true;
     131             : }
     132             : 
     133             : /*
     134             :  * This should be called only once, from PLy_initialize. Initialize the Python
     135             :  * interpreter and global data.
     136             :  */
     137             : static void
     138          46 : PLy_init_interp(void)
     139             : {
     140             :     static PyObject *PLy_interp_safe_globals = NULL;
     141             :     PyObject   *mainmod;
     142             : 
     143          46 :     mainmod = PyImport_AddModule("__main__");
     144          46 :     if (mainmod == NULL || PyErr_Occurred())
     145           0 :         PLy_elog(ERROR, "could not import \"__main__\" module");
     146          46 :     Py_INCREF(mainmod);
     147          46 :     PLy_interp_globals = PyModule_GetDict(mainmod);
     148          46 :     PLy_interp_safe_globals = PyDict_New();
     149          46 :     if (PLy_interp_safe_globals == NULL)
     150           0 :         PLy_elog(ERROR, NULL);
     151          46 :     PyDict_SetItemString(PLy_interp_globals, "GD", PLy_interp_safe_globals);
     152          46 :     Py_DECREF(mainmod);
     153          46 :     if (PLy_interp_globals == NULL || PyErr_Occurred())
     154           0 :         PLy_elog(ERROR, "could not initialize globals");
     155          46 : }
     156             : 
     157             : Datum
     158         494 : plpython3_validator(PG_FUNCTION_ARGS)
     159             : {
     160         494 :     Oid         funcoid = PG_GETARG_OID(0);
     161             :     HeapTuple   tuple;
     162             :     Form_pg_proc procStruct;
     163             :     bool        is_trigger;
     164             : 
     165         494 :     if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
     166           0 :         PG_RETURN_VOID();
     167             : 
     168         494 :     if (!check_function_bodies)
     169           2 :         PG_RETURN_VOID();
     170             : 
     171             :     /* Do this only after making sure we need to do something */
     172         492 :     PLy_initialize();
     173             : 
     174             :     /* Get the new function's pg_proc entry */
     175         492 :     tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
     176         492 :     if (!HeapTupleIsValid(tuple))
     177           0 :         elog(ERROR, "cache lookup failed for function %u", funcoid);
     178         492 :     procStruct = (Form_pg_proc) GETSTRUCT(tuple);
     179             : 
     180         492 :     is_trigger = PLy_procedure_is_trigger(procStruct);
     181             : 
     182         492 :     ReleaseSysCache(tuple);
     183             : 
     184             :     /* We can't validate triggers against any particular table ... */
     185         492 :     PLy_procedure_get(funcoid, InvalidOid, is_trigger);
     186             : 
     187         490 :     PG_RETURN_VOID();
     188             : }
     189             : 
     190             : Datum
     191        1356 : plpython3_call_handler(PG_FUNCTION_ARGS)
     192             : {
     193             :     bool        nonatomic;
     194             :     Datum       retval;
     195             :     PLyExecutionContext *exec_ctx;
     196             :     ErrorContextCallback plerrcontext;
     197             : 
     198        1356 :     PLy_initialize();
     199             : 
     200        2820 :     nonatomic = fcinfo->context &&
     201        1372 :         IsA(fcinfo->context, CallContext) &&
     202          16 :         !castNode(CallContext, fcinfo->context)->atomic;
     203             : 
     204             :     /* Note: SPI_finish() happens in plpy_exec.c, which is dubious design */
     205        1356 :     if (SPI_connect_ext(nonatomic ? SPI_OPT_NONATOMIC : 0) != SPI_OK_CONNECT)
     206           0 :         elog(ERROR, "SPI_connect failed");
     207             : 
     208             :     /*
     209             :      * Push execution context onto stack.  It is important that this get
     210             :      * popped again, so avoid putting anything that could throw error between
     211             :      * here and the PG_TRY.
     212             :      */
     213        1356 :     exec_ctx = PLy_push_execution_context(!nonatomic);
     214             : 
     215        1356 :     PG_TRY();
     216             :     {
     217        1356 :         Oid         funcoid = fcinfo->flinfo->fn_oid;
     218             :         PLyProcedure *proc;
     219             : 
     220             :         /*
     221             :          * Setup error traceback support for ereport().  Note that the PG_TRY
     222             :          * structure pops this for us again at exit, so we needn't do that
     223             :          * explicitly, nor do we risk the callback getting called after we've
     224             :          * destroyed the exec_ctx.
     225             :          */
     226        1356 :         plerrcontext.callback = plpython_error_callback;
     227        1356 :         plerrcontext.arg = exec_ctx;
     228        1356 :         plerrcontext.previous = error_context_stack;
     229        1356 :         error_context_stack = &plerrcontext;
     230             : 
     231        1356 :         if (CALLED_AS_TRIGGER(fcinfo))
     232          74 :         {
     233          92 :             Relation    tgrel = ((TriggerData *) fcinfo->context)->tg_relation;
     234             :             HeapTuple   trv;
     235             : 
     236          92 :             proc = PLy_procedure_get(funcoid, RelationGetRelid(tgrel), true);
     237          92 :             exec_ctx->curr_proc = proc;
     238          92 :             trv = PLy_exec_trigger(fcinfo, proc);
     239          74 :             retval = PointerGetDatum(trv);
     240             :         }
     241             :         else
     242             :         {
     243        1264 :             proc = PLy_procedure_get(funcoid, InvalidOid, false);
     244        1258 :             exec_ctx->curr_proc = proc;
     245        1258 :             retval = PLy_exec_function(fcinfo, proc);
     246             :         }
     247             :     }
     248         186 :     PG_CATCH();
     249             :     {
     250         186 :         PLy_pop_execution_context();
     251         186 :         PyErr_Clear();
     252         186 :         PG_RE_THROW();
     253             :     }
     254        1170 :     PG_END_TRY();
     255             : 
     256             :     /* Destroy the execution context */
     257        1170 :     PLy_pop_execution_context();
     258             : 
     259        1170 :     return retval;
     260             : }
     261             : 
     262             : Datum
     263          42 : plpython3_inline_handler(PG_FUNCTION_ARGS)
     264             : {
     265          42 :     LOCAL_FCINFO(fake_fcinfo, 0);
     266          42 :     InlineCodeBlock *codeblock = (InlineCodeBlock *) DatumGetPointer(PG_GETARG_DATUM(0));
     267             :     FmgrInfo    flinfo;
     268             :     PLyProcedure proc;
     269             :     PLyExecutionContext *exec_ctx;
     270             :     ErrorContextCallback plerrcontext;
     271             : 
     272          42 :     PLy_initialize();
     273             : 
     274             :     /* Note: SPI_finish() happens in plpy_exec.c, which is dubious design */
     275          42 :     if (SPI_connect_ext(codeblock->atomic ? 0 : SPI_OPT_NONATOMIC) != SPI_OK_CONNECT)
     276           0 :         elog(ERROR, "SPI_connect failed");
     277             : 
     278         210 :     MemSet(fcinfo, 0, SizeForFunctionCallInfo(0));
     279         294 :     MemSet(&flinfo, 0, sizeof(flinfo));
     280          42 :     fake_fcinfo->flinfo = &flinfo;
     281          42 :     flinfo.fn_oid = InvalidOid;
     282          42 :     flinfo.fn_mcxt = CurrentMemoryContext;
     283             : 
     284        1722 :     MemSet(&proc, 0, sizeof(PLyProcedure));
     285          42 :     proc.mcxt = AllocSetContextCreate(TopMemoryContext,
     286             :                                       "__plpython_inline_block",
     287             :                                       ALLOCSET_DEFAULT_SIZES);
     288          42 :     proc.pyname = MemoryContextStrdup(proc.mcxt, "__plpython_inline_block");
     289          42 :     proc.langid = codeblock->langOid;
     290             : 
     291             :     /*
     292             :      * This is currently sufficient to get PLy_exec_function to work, but
     293             :      * someday we might need to be honest and use PLy_output_setup_func.
     294             :      */
     295          42 :     proc.result.typoid = VOIDOID;
     296             : 
     297             :     /*
     298             :      * Push execution context onto stack.  It is important that this get
     299             :      * popped again, so avoid putting anything that could throw error between
     300             :      * here and the PG_TRY.
     301             :      */
     302          42 :     exec_ctx = PLy_push_execution_context(codeblock->atomic);
     303             : 
     304          42 :     PG_TRY();
     305             :     {
     306             :         /*
     307             :          * Setup error traceback support for ereport().
     308             :          * plpython_inline_error_callback doesn't currently need exec_ctx, but
     309             :          * for consistency with plpython3_call_handler we do it the same way.
     310             :          */
     311          42 :         plerrcontext.callback = plpython_inline_error_callback;
     312          42 :         plerrcontext.arg = exec_ctx;
     313          42 :         plerrcontext.previous = error_context_stack;
     314          42 :         error_context_stack = &plerrcontext;
     315             : 
     316          42 :         PLy_procedure_compile(&proc, codeblock->source_text);
     317          42 :         exec_ctx->curr_proc = &proc;
     318          42 :         PLy_exec_function(fake_fcinfo, &proc);
     319             :     }
     320          22 :     PG_CATCH();
     321             :     {
     322          22 :         PLy_pop_execution_context();
     323          22 :         PLy_procedure_delete(&proc);
     324          22 :         PyErr_Clear();
     325          22 :         PG_RE_THROW();
     326             :     }
     327          20 :     PG_END_TRY();
     328             : 
     329             :     /* Destroy the execution context */
     330          20 :     PLy_pop_execution_context();
     331             : 
     332             :     /* Now clean up the transient procedure we made */
     333          20 :     PLy_procedure_delete(&proc);
     334             : 
     335          20 :     PG_RETURN_VOID();
     336             : }
     337             : 
     338             : static bool
     339         492 : PLy_procedure_is_trigger(Form_pg_proc procStruct)
     340             : {
     341         492 :     return (procStruct->prorettype == TRIGGEROID);
     342             : }
     343             : 
     344             : static void
     345         904 : plpython_error_callback(void *arg)
     346             : {
     347         904 :     PLyExecutionContext *exec_ctx = (PLyExecutionContext *) arg;
     348             : 
     349         904 :     if (exec_ctx->curr_proc)
     350             :     {
     351         898 :         if (exec_ctx->curr_proc->is_procedure)
     352           8 :             errcontext("PL/Python procedure \"%s\"",
     353             :                        PLy_procedure_name(exec_ctx->curr_proc));
     354             :         else
     355         890 :             errcontext("PL/Python function \"%s\"",
     356             :                        PLy_procedure_name(exec_ctx->curr_proc));
     357             :     }
     358         904 : }
     359             : 
     360             : static void
     361          56 : plpython_inline_error_callback(void *arg)
     362             : {
     363          56 :     errcontext("PL/Python anonymous code block");
     364          56 : }
     365             : 
     366             : PLyExecutionContext *
     367        2820 : PLy_current_execution_context(void)
     368             : {
     369        2820 :     if (PLy_execution_contexts == NULL)
     370           0 :         elog(ERROR, "no Python function is currently executing");
     371             : 
     372        2820 :     return PLy_execution_contexts;
     373             : }
     374             : 
     375             : MemoryContext
     376        1558 : PLy_get_scratch_context(PLyExecutionContext *context)
     377             : {
     378             :     /*
     379             :      * A scratch context might never be needed in a given plpython procedure,
     380             :      * so allocate it on first request.
     381             :      */
     382        1558 :     if (context->scratch_ctx == NULL)
     383         866 :         context->scratch_ctx =
     384         866 :             AllocSetContextCreate(TopTransactionContext,
     385             :                                   "PL/Python scratch context",
     386             :                                   ALLOCSET_DEFAULT_SIZES);
     387        1558 :     return context->scratch_ctx;
     388             : }
     389             : 
     390             : static PLyExecutionContext *
     391        1398 : PLy_push_execution_context(bool atomic_context)
     392             : {
     393             :     PLyExecutionContext *context;
     394             : 
     395             :     /* Pick a memory context similar to what SPI uses. */
     396             :     context = (PLyExecutionContext *)
     397        1398 :         MemoryContextAlloc(atomic_context ? TopTransactionContext : PortalContext,
     398             :                            sizeof(PLyExecutionContext));
     399        1398 :     context->curr_proc = NULL;
     400        1398 :     context->scratch_ctx = NULL;
     401        1398 :     context->next = PLy_execution_contexts;
     402        1398 :     PLy_execution_contexts = context;
     403        1398 :     return context;
     404             : }
     405             : 
     406             : static void
     407        1398 : PLy_pop_execution_context(void)
     408             : {
     409        1398 :     PLyExecutionContext *context = PLy_execution_contexts;
     410             : 
     411        1398 :     if (context == NULL)
     412           0 :         elog(ERROR, "no Python function is currently executing");
     413             : 
     414        1398 :     PLy_execution_contexts = context->next;
     415             : 
     416        1398 :     if (context->scratch_ctx)
     417         832 :         MemoryContextDelete(context->scratch_ctx);
     418        1398 :     pfree(context);
     419        1398 : }

Generated by: LCOV version 1.14